Существует ли практическое преимущество для кастинга Нулевого указателя к объекту и вызову одной из его функций членства?

Хорошо, таким образом, я знаю, что технически это - неопределенное поведение, но тем не менее, я видел это несколько раз в производственном коде. И исправьте меня, если я неправ, но я также услышал, что некоторые люди используют эту "функцию" поскольку несколько законная замена для недостающего аспекта текущего стандарта 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-стиля к сожалению, люди все еще предпочитают вводить меньше, если они могут.

9
задан M.M 13 November 2014 в 10:04
поделиться

7 ответов

Первый случай ничего не вызывает. Он принимает адрес . Это определенная разрешенная операция. Он дает смещение в байтах от начала объекта до указанного поля. Это очень, очень распространенная практика, поскольку подобные смещения необходимы очень часто. В конце концов, не все объекты могут быть созданы в стеке.

Второй случай достаточно глупый. Разумно было бы объявить этот метод статическим.

9
ответ дан 4 December 2019 в 08:15
поделиться

Неопределенное поведение - это неопределенное поведение. Эти уловки «работают» для вашего конкретного компилятора? ну возможно. будут ли они работать на следующей итерации. или для другого компилятора? Возможно, нет. Вы платите деньги и делаете свой выбор. Могу только сказать, что за почти 25 лет программирования на C ++ я никогда не чувствовал необходимости делать что-либо из этого.

2
ответ дан 4 December 2019 в 08:15
поделиться

Все остальные хорошо поработали, повторив, что поведение не определено. Но давайте представим, что это не так, и что 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
}

Итак, на самом деле, даже если бы это не было неопределенным поведением, это бесполезное поведение.

4
ответ дан 4 December 2019 в 08:15
поделиться

Я не вижу никакой пользы от ((Result *) 0) -> stat (); - это уродливый хак, который, скорее всего, сломается раньше, чем позже. Правильный подход C ++ предполагает использование статического метода Result :: stat () .

offsetof (), с другой стороны, является допустимым, поскольку макрос offsetof () никогда не вызывает метод или не обращается к члену, а выполняет только вычисления адреса.

4
ответ дан 4 December 2019 в 08:15
поделиться

Хотя библиотеки, предназначенные для определенных реализаций C ++, могут это делать, это не означает, что в целом это «законно».

Это прекрасно работает! Он избегает разыменования нулевого указателя , проверяя его существование , и не пытается получить доступ к членам класса в блоке else . Пока эти средства защиты находятся на месте , это законный код, не так ли?

Нет, потому что, хотя он может нормально работать в некоторых реализациях C ++, совершенно нормально, что он не работает ни в одной соответствующей реализации C ++ .

3
ответ дан 4 December 2019 в 08:15
поделиться

Относительно утверждения:

Он избегает разыменования нулевого указателя, проверяя его существование, и не пытается получить доступ к членам класса в еще блок. Пока эта охрана на месте, это законный код, не так ли?

Код не легитимный. Нет никакой гарантии, что компилятор и / или среда выполнения действительно вызовут метод, когда указатель равен NULL. Проверка в методе не поможет, потому что вы не можете предположить, что метод действительно будет вызван с указателем NULL this .

1
ответ дан 4 December 2019 в 08:15
поделиться

Разыменование нулевого указателя является неопределенным поведением, и если вы это сделаете, может произойти что угодно. Не делайте этого, если вам нужна работающая программа.

Тот факт, что он не дает сбой сразу в одном конкретном тестовом примере, не означает, что он не доставит вам всевозможных проблем.

3
ответ дан 4 December 2019 в 08:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: