Роздуми над разыменованием нульового покажчика

Виявляється, що питання, коректний чи ні ось такий код &((T*)(0)->x), дуже непростий. Вирішив написати про це маленьку замітку.

У недавній статті про перевірку ядра Linux з допомогою аналізатора PVS-Studio, я написав, що знайшов ось такий фрагмент коду:
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;
....
}

Також в статті я написав, що такий код, на мій погляд, є некоректним. Подробиці можна подивитися в статті.

Після цього мені посипалися на пошту листи про те, що я неправий, і цей код зовсім коректний. Багато вказали, що якщо podhd == 0, то цей код по суті реалізує ідіому «offsetof», і нічого поганого статися не може. Щоб не писати багато відповідей я вирішив оформити відповідь у вигляді маленького поста в блозі.

Природно я вирішив вивчити дану тему детальніше. Але, якщо чесно, в результаті я тільки ще більше заплутався. Тому я не дам вам точну відповідь, можна так писати чи ні. Я тільки запропоную деякі посилання і поділюся своєю думкою.

Коли я писав статтю про перевірку Linux, я міркував так.

Будь розіменування нульового покажчика — це невизначене поведінку. Одним з проявів невизначеного поведінки може стати така оптимізація коду, коли перевірка (podhd == NULL) зникне. Саме такий варіант розвитку подій я і описав в статті.

У листах деякі розробники написали, що не змогли досягти такої поведінки на своїх компіляторах. Однак, це нічого не доводить. Очікувана правильна робота програми — це просто один з варіантів невизначеного поведінки.

Деякі також написали, що саме так влаштований макрос ffsetof():
#define offsetof(st, m) ((size_t)(&((st *)0)->m))

Однак і це нічого не доводить. Такі макроси спеціально зроблені так, щоб працювати правильно в потрібному компіляторі. Якщо ми напишемо схожий код, зовсім необов'язково, що він буде працювати.

Більше того, тут компілятор явно бачить 0 і може вгадати, що від нього хоче програміст. Коли 0 зберігається в змінної це зовсім інша справа, і компілятор може повести себе несподіваним чином.

Ось, що про offsetof сказано в Wikipedia:

The «traditional» implementation of the macro relied on the compiler being not especially picky about pointers; it obtained the offset of a member by specifying a hypothetical structure that begins at address zero:

#define offsetof(st, m) ((size_t)(&((st *)0)->m))

This works by casting a null pointer into a pointer to structure st, obtaining and then the address of member m within said structure. While this works correctly in many компілятори, it has undefined behavior according to the C standard, since it involves a dereference of a null pointer (although, one might argue that no dereferencing takes place, because the whole expression is calculated at compile time). It also tends to produce confusing compiler diagnostics if one of the arguments is misspelled. Some modern компілятори (such as GCC) define the macro using a special form instead, e.g.

#define offsetof(st, m) __builtin_offsetof(st, m)

Як бачите, згідно Wikipedia я прав. Так писати не можна. Це undefined behavior. Так само вважають деякі на сайті StackOverflow: Address of members of a struct via NULL pointer.

Однак, мене бентежить те, що, хоча всі говорять про невизначений поведінка, ніде немає точного роз'яснення на цей рахунок. Наприклад, у Wikipedia стоїть позначка, що твердження вимагає підтвердження [citation needed].

На форумах багато разів обговорювалися схожі питання, але ніде я не побачив однозначного пояснення, підтвердженого посиланнями на стандарт Сі чи Сі++.

Є ще ось таке старе обговорення стандарту, яке теж не додало ясності: 232. Is indirection through a null pointer undefined behavior?

Отже, на даний момент остаточно це питання мені не ясний. Проте, я як і раніше вважаю, що цей код поганий і його слід отрефакторить.

Якщо хтось пришле мені хороші примітки на цю тему, то я додам їх у кінець цієї статті.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.