- PVSM.RU - https://www.pvsm.ru -
Ненароком я породил большую дискуссию, касающуюся того, допустимо ли использовать в Си/Си++ выражение &P->m_foo, если P является нулевым указателем. Программисты разделились на два лагеря. Одни уверенно доказывали, что так писать нельзя, другие столь же уверенно утверждали, что можно. Приводились различные аргументы и ссылки. И я понял, что нужно внести окончательную ясность в этот вопрос. Для этого я обратился к экспертам Microsoft MVP и разработчикам Visual C++, общающимся через закрытый список рассылки. Они помогли подготовить эту статью, и я представляю её всем желающим. Для нетерпеливых: этот код не корректен.
Все началось со статьи [1] о проверке ядра Linux с помощью анализатора PVS-Studio. Но сама проверка ядра тут ни причём. Дело в том, что в статье я привёл следующий фрагмент из кода Linux:
static int podhd_try_init(struct usb_interface *interface,
struct usb_line6_podhd *podhd)
{
int err;
struct usb_line6 *line6 = &podhd->line6;
if ((interface == NULL) || (podhd == NULL))
return -ENODEV;
....
}
Я назвал этот код опасным, так ка посчитал, что здесь имеет место неопределённое поведение [2].
По этому поводу я получил много возражений от читателей и даже одно время был готов поддаться на их убедительные речи в письмах и комментариях. Например, в качестве доказательства корректности кода приводили устройство макроса offsetof [3], который часто реализован так:
#define offsetof(st, m) ((size_t)(&((st *)0)->m))
Здесь имеет место разыменование нулевого указателя, но код успешно работает. Были и другие письма с рассуждениями того, что раз нет доступа по нулевому указателю, то нет и проблемы.
Хотя я и доверчивый, но стараюсь проверять информацию. Я начал разбираться с этой темой и в результате написал небольшую статью: "Размышления над разыменованием нулевого указателя [4]".
По всему выходило, что я был прав. Так писать нельзя. Однако я не смог окончательно обосновать свою позицию и привести нужные ссылки на стандарт.
После статьи вновь последовали письма с возражениями, и я понял, что надо разобраться с данной темой окончательно. Я обратился с вопросом к экспертам, чтобы узнать их мнение. Эта статья является их обобщенным ответом.
Выражение '&podhd->line6' является неопределенным поведением в языке C в том случае, если 'podhd' — нулевой указатель.
Вот что говорится про оператор взятия адреса '&' в стандарте C99 (Раздел 6.5.3.2 «Операторы взятия адреса и разыменовывания»):
Операнд унарного оператора & должен быть либо указателем функции, либо результатом оператора [] или унарного оператора *, либо lvalue-выражением, указывающим на объект, который не является битовым полем и не содержит в объявлении спецификатора регистрового класса памяти.
Выражение 'podhd->line6' однозначно не является указателем функции, результатом оператора [] или *. Это как раз lvalue-выражение. Однако, когда указатель 'podhd' равен нулю, выражение не указывает на объект, поскольку в Разделе 6.3.2.3 «Указатели» сказано следующее:
Если константа нулевого указателя приводится к типу указателей, то результирующий указатель, называемый нулевым, гарантированно будет не равен указателю на любой объект или функцию.
Если «lvalue-выражение не указывает на объект при своем вычислении, возникает неопределенное поведение» (Стандарт C99, Раздел 6.3.2.1 «Lvalue-выражения, массивы и указатели функций»):
lvalue — это выражение объектного типа или неполного типа, отличного от void; если lvalue-выражение не указывает на объект при своем вычислении, возникает неопределенное поведение.
Ещё раз кратко:
Когда оператор -> был применен к указателю, его результатом стало lvalue-выражение, для которого не существует объекта, и в результате мы имеем дело с неопределенным поведением.
В языке С++ всё обстоит точно также. Выражение '&podhd->line6' является неопределенным поведением в языке C++ в том случае, если 'podhd' — нулевой указатель.
С толку немного сбивает дискуссия на WG21 (232. Is indirection through a null pointer undefined behavior? [5]), на которую я ссылался в предыдущей статье. Там настаивают, будто бы такое выражение не является неопределенным поведением. Однако никто так и не нашел никаких правил в стандартах C++, которые разрешали бы использовать «poldh->line6», когда «polhd» — нулевой указатель.
Указатель «polhd» нарушает основное ограничение (Раздел 5.2.5/4, второй пункт в списке) о том, что он должен указывать на объект. Ни один объект в C++ не может иметь адреса nullptr.
struct usb_line6 *line6 = &podhd->line6;
Этот код является некорректным в языке Си и Си++, если указатель podhd равен 0. Если указатель равен 0, то возникает неопределённое поведение.
То, что программа может работать, является везением. Неопределённое поведение может проявить себя, как угодно. В том числе, программа может работать так, как хотел программист. Это один из частных случае, но не более того.
Так писать нельзя. Указатель должен быть проверен до разыменования.
В подготовке статьи мне помогли эксперты, сомневаться в компетенции, которых нет повода. Я благодарен за помощь в написании статьи следующим людям:
Автор: Andrey2008
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/83065
Ссылки в тексте:
[1] статьи: http://www.viva64.com/ru/b/0299/
[2] неопределённое поведение: http://www.viva64.com/ru/t/0066/
[3] offsetof: http://www.viva64.com/go.php?url=1481
[4] Размышления над разыменованием нулевого указателя: http://habrahabr.ru/company/pvs-studio/blog/247973/
[5] 232. Is indirection through a null pointer undefined behavior?: http://www.viva64.com/go.php?url=1484
[6] What Every C Programmer Should Know About Undefined Behavior #2/3: http://www.viva64.com/go.php?url=1499
[7] Fun with NULL pointers: http://www.viva64.com/go.php?url=1500
[8] StackOverflow: http://www.viva64.com/go.php?url=1493
[9] StackOverflow: http://www.viva64.com/go.php?url=1494
[10] StackOverflow: http://www.viva64.com/go.php?url=1495
[11] Неопределённое поведение: http://www.viva64.com/go.php?url=1496
[12] 1: http://www.viva64.com/go.php?url=750
[13] 2: http://www.viva64.com/go.php?url=1497
[14] 3: http://www.viva64.com/go.php?url=1498
[15] 2: http://www.viva64.com/go.php?url=1501
[16] Источник: http://habrahabr.ru/post/250701/
Нажмите здесь для печати.