Есть ли практическое преимущество приведения нулевого указателя на объект и вызова одной из его функций-членов? - PullRequest
9 голосов
/ 03 апреля 2010

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

Ответы [ 7 ]

9 голосов
/ 03 апреля 2010

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

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

4 голосов
/ 03 апреля 2010

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

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

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

3 голосов
/ 03 апреля 2010

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

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

3 голосов
/ 03 апреля 2010

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

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

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

2 голосов
/ 03 апреля 2010

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

1 голос
/ 03 апреля 2010

По поводу заявления:

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

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

...