Оба (a)
и (b)
приводят к неопределенному поведению. Вызов функции-члена через нулевой указатель всегда является неопределенным. Если функция статическая, она также технически не определена, но есть некоторые споры.
Прежде всего необходимо понять, почему разыменование нулевого указателя является неопределенным. В C ++ 03 здесь действительно есть некоторая двусмысленность.
Хотя «разыменование нулевого указателя приводит к неопределенному поведению» упоминается в примечаниях как в §1.9 / 4, так и в §8.3.2 / 4, это никогда не указывается явно. (Примечания не являются нормативными.)
Однако можно попытаться вывести его из §3.10 / 2:
Lvalue относится к объекту или функции.
При разыменовании результатом является lvalue. Нулевой указатель не относится к объекту, поэтому, когда мы используем lvalue, мы имеем неопределенное поведение. Проблема в том, что предыдущее предложение никогда не формулируется, так что же значит «использовать» lvalue? Просто даже сгенерировать его вообще или использовать в более формальном смысле для преобразования lvalue-to-rvalue?
Тем не менее, его определенно нельзя преобразовать в rvalue (§4.1 / 1):
Если объект, на который ссылается lvalue, не является объектом типа T и не является объектом типа, производного от T, или если объект неинициализирован, программа, которая требует этого преобразования, имеет неопределенное поведение.
Здесь определенно неопределенное поведение.
Неопределенность возникает из-за того, является ли неопределенное поведение уважительным, но не использует ли значение из недопустимого указателя (то есть получить lvalue, но не преобразовать его в rvalue). Если нет, то int *i = 0; *i; &(*i);
это хорошо определено. Это активный вопрос .
Итак, у нас есть строгое представление «разыменовать нулевой указатель, получить неопределенное поведение» и слабое представление «использовать разыменованный нулевой указатель, получить неопределенное поведение».
Теперь рассмотрим вопрос.
Да, (a)
приводит к неопределенному поведению. Фактически, если this
имеет значение null, то независимо от содержимого функции результат не определен.
Это следует из §5.2.5 / 3:
Если E1
имеет тип «указатель на класс X», то выражение E1->E2
преобразуется в эквивалентную форму(*(E1)).E2;
*(E1)
приведет к неопределенному поведению со строгой интерпретацией и .E2
преобразует его в rvalue, делая неопределенное поведение для слабой интерпретации.
Отсюда также следует, что это неопределенное поведение непосредственно из (§9.3.1 / 1):
Если нестатическая функция-член класса X вызывается для объекта, который не относится к типу X или типу, производному от X, поведение не определено.
Что касается статических функций, разница между строгой и слабой интерпретацией. Строго говоря, это не определено:
На статический член можно ссылаться с использованием синтаксиса доступа к члену класса, и в этом случае вычисляется объект-выражение.
То есть он оценивается так же, как если бы он был нестатическим, и мы снова разыменовываем нулевой указатель с помощью (*(E1)).E2
.
Однако, поскольку E1
он не используется в вызове статической функции-члена, если мы используем слабую интерпретацию, вызов будет четко определен. *(E1)
приводит к lvalue, статическая функция разрешается, *(E1)
отбрасывается и функция вызывается. Преобразование lvalue-to-rvalue отсутствует, поэтому неопределенное поведение отсутствует.
В C ++ 0x, начиная с n3126, неоднозначность сохраняется. А пока будьте осторожны: используйте строгую интерпретацию.