Рассмотрите следующий код:
#include <iostream>
struct foo
{
// (a):
void bar() { std::cout << "gman was here" << std::endl; }
// (b):
void baz() { x = 5; }
int x;
};
int main()
{
foo* f = 0;
f->bar(); // (a)
f->baz(); // (b)
}
Мы ожидаем (b)
отказать, потому что нет никакого члена-корреспондента x
для нулевого указателя. На практике, (a)
не отказывает потому что this
указатель никогда не используется.
Поскольку (b)
разыменовывает this
указатель ((*this).x = 5;
), и this
является пустым, программа вводит неопределенное поведение, поскольку разыменование пустого указателя, как всегда говорят, является неопределенным поведением.
Делает (a)
результат в неопределенном поведении? Что относительно если обе функции (и x
) статичны?
Оба (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);
определен правильно. Это активная проблема .
Итак, у нас есть строгое представление «разыменовать нулевой указатель, получить неопределенное поведение» и слабое представление «использовать разыменованный нулевой указатель, получить неопределенное поведение».
Теперь рассмотрим вопрос.
Да, (a)
приводит к неопределенному поведению. Фактически, если this
имеет значение null, то независимо от содержимого функции результат не определен.
Это следует из §5.2.5 / 3:
Если
E1
имеет тип «указатель на класс X», то выражениеE1-> E2
преобразуется в эквивалентная форма(* (E1)). E2;
* (E1)
приведет к неопределенному поведению со строгой интерпретацией, а .E2
преобразует его в r-значение, делая его неопределенным поведением для слабой интерпретации.
Отсюда также следует, что это неопределенное поведение непосредственно из (§9.3.1 / 1):
Если нестатическая функция-член класса X вызывается для объекта, который не относится к типу X, или к производному типу из X поведение не определено.
Что касается статических функций, разница между строгой и слабой интерпретацией. Строго говоря, он не определен:
На статический член можно ссылаться с использованием синтаксиса доступа к члену класса, и в этом случае вычисляется выражение объекта.
То есть он оценивается так же, как если бы он был нестатическим, и мы снова разыменовываем нулевой указатель с помощью (* (E1)). E2
.
Однако, поскольку E1
не используется в вызове статической функции-члена, если мы используем слабую интерпретацию, вызов будет четко определен. * (E1)
приводит к lvalue, статическая функция разрешается, * (E1)
отбрасывается, и функция вызывается. Преобразования lvalue-to-rvalue нет, поэтому нет неопределенного поведения.
В C ++ 0x, начиная с n3126, неоднозначность сохраняется. А пока будьте осторожны: используйте строгую интерпретацию.