Хорошо, таким образом, я знаю, что технически это - неопределенное поведение, но тем не менее, я видел это несколько раз в производственном коде. И исправьте меня, если я неправ, но я также услышал, что некоторые люди используют эту "функцию" поскольку несколько законная замена для недостающего аспекта текущего стандарта C++, а именно, неспособность получить адрес (хорошо, смещение действительно) функции членства. Например, это - вне популярной реализации PCRE (совместимое с Perl Регулярное выражение) библиотека:
#ifndef offsetof
#define offsetof(p_type,field) ((size_t)&(((p_type *)0)->field))
#endif
Можно дебатировать, допустима ли эксплуатация такой тонкости языка в случае как это или не или даже необходима, но я также видел, что это использовало как это:
struct Result
{
void stat()
{
if(this)
// do something...
else
// do something else...
}
};
// ...somewhere else in the code...
((Result*)0)->stat();
Это работает просто великолепно! Это избегает, чтобы нулевой указатель разыменовал путем тестирования на существование this
, и это не пытается получить доступ к участникам класса в else
блок. Пока эта защита существует, это - законный код, правильно? Таким образом, вопрос остается: существует ли случай практического применения, где можно было бы извлечь выгоду из использования такой конструкции? Я особенно обеспокоен вторым случаем, так как первый случай является большим количеством обходного решения для ограничения языка. Или это?
PS. Извините за броски C-стиля к сожалению, люди все еще предпочитают вводить меньше, если они могут.
Первый случай ничего не вызывает. Он принимает адрес . Это определенная разрешенная операция. Он дает смещение в байтах от начала объекта до указанного поля. Это очень, очень распространенная практика, поскольку подобные смещения необходимы очень часто. В конце концов, не все объекты могут быть созданы в стеке.
Второй случай достаточно глупый. Разумно было бы объявить этот метод статическим.
Неопределенное поведение - это неопределенное поведение. Эти уловки «работают» для вашего конкретного компилятора? ну возможно. будут ли они работать на следующей итерации. или для другого компилятора? Возможно, нет. Вы платите деньги и делаете свой выбор. Могу только сказать, что за почти 25 лет программирования на C ++ я никогда не чувствовал необходимости делать что-либо из этого.
Все остальные хорошо поработали, повторив, что поведение не определено. Но давайте представим, что это не так, и что p-> member
может вести себя согласованным образом при определенных обстоятельствах, даже если p
не является допустимым указателем.
Ваша вторая конструкция по-прежнему почти бесполезна. С точки зрения дизайна, вы, вероятно, сделали что-то не так, если отдельная функция может выполнять свою работу как с доступом к членам, так и без доступа, и если она может , то разделение статической части кода на отдельную статическую будет гораздо более разумным, чем ожидание, что ваши пользователи создадут нулевой указатель для работы.
С точки зрения безопасности, вы защитили лишь небольшую часть способов создания недопустимого указателя this
. Для начала есть неинициализированные указатели:
Result* p;
p->stat(); //Oops, 'this' is some random value
Есть указатели, которые были инициализированы, но все еще недействительны:
Result* p = new Result;
delete p;
p->stat(); //'this' points to "safe" memory, but the data doesn't belong to you
И даже если вы всегда инициализируете свои указатели и абсолютно никогда случайно не повторно используете свободную память:
struct Struct {
int i;
Result r;
}
int main() {
((Struct*)0)->r.stat(); //'this' is likely sizeof(int), not 0
}
Итак, на самом деле, даже если бы это не было неопределенным поведением, это бесполезное поведение.
Я не вижу никакой пользы от ((Result *) 0) -> stat ();
- это уродливый хак, который, скорее всего, сломается раньше, чем позже. Правильный подход C ++ предполагает использование статического метода Result :: stat ()
.
offsetof (), с другой стороны, является допустимым, поскольку макрос offsetof () никогда не вызывает метод или не обращается к члену, а выполняет только вычисления адреса.
Хотя библиотеки, предназначенные для определенных реализаций C ++, могут это делать, это не означает, что в целом это «законно».
Это прекрасно работает! Он избегает разыменования нулевого указателя , проверяя его существование , и не пытается получить доступ к членам класса в блоке else . Пока эти средства защиты находятся на месте , это законный код, не так ли?
Нет, потому что, хотя он может нормально работать в некоторых реализациях C ++, совершенно нормально, что он не работает ни в одной соответствующей реализации C ++ .
Относительно утверждения:
Он избегает разыменования нулевого указателя, проверяя его существование, и не пытается получить доступ к членам класса в еще блок. Пока эта охрана на месте, это законный код, не так ли?
Код не легитимный. Нет никакой гарантии, что компилятор и / или среда выполнения действительно вызовут метод, когда указатель равен NULL. Проверка в методе не поможет, потому что вы не можете предположить, что метод действительно будет вызван с указателем NULL
this
.
Разыменование нулевого указателя является неопределенным поведением, и если вы это сделаете, может произойти что угодно. Не делайте этого, если вам нужна работающая программа.
Тот факт, что он не дает сбой сразу в одном конкретном тестовом примере, не означает, что он не доставит вам всевозможных проблем.