Дефиниран ли е достъпът на член до нулев указател в C++?

Изчисляването на адреси върху дефинирано поведение на нулев указател ли е в C++? Ето една проста примерна програма.

struct A { int x; };
int main() {
  A* p = nullptr;
  &(p->x);  // is this undefined behavior?
  return 0;
}

Благодаря.

РЕДАКТИРАНЕ Абонаментът е разгледан в този друг въпрос.


person Gregory    schedule 12.06.2020    source източник
comment
Прилича ми на UB. (За първия пример имахте предвид „&(p-›x);“?)   -  person Eljay    schedule 12.06.2020
comment
@cigien Благодаря за показалеца (без игра на думи). Това отговаря на втория въпрос, но не и на първия относно достъпа на членовете.   -  person Gregory    schedule 12.06.2020
comment
Определено UB, няма обект там, където сочи p, така че дори преди да вземете адреса, да направите p->x е същото като (*p).x, което е UB.   -  person cigien    schedule 12.06.2020
comment
Тук задавате два различни въпроса. Членският достъп и абонаментът не са едно и също нещо. Един от въпросите е по-нюансиран от другия.   -  person Brian Bi    schedule 12.06.2020
comment
Това отговаря ли на въпроса ви: stackoverflow.com/questions/2474018/   -  person M.M    schedule 12.06.2020


Отговори (1)


&(p->x);  // is this undefined behavior?

Стандартът е малко неясен по отношение на това:

[expr.ref] ... Изразът E1-›E2 се преобразува в еквивалентната форма (*(E1)).E2;

[expr.unary.op] Унарният * оператор ... резултатът е lvalue, отнасящ се до обекта ... към който сочи изразът.

В раздела не се споменава изрично UB. Цитираното правило изглежда противоречи на факта, че нулевият указател не сочи към никакъв обект. Това може да се тълкува, че да, поведението е недефинирано.

[expr.unary.op] Резултатът от унарния & оператор е указател към неговия операнд. ... ако операндът е lvalue от тип T, резултантният израз е prvavalue от тип „указател към T“, чийто резултат е указател към посочения обект ([intro.memory]).

Отново не съществува определен обект. Обърнете внимание, че в нито един момент операндът lvalue не се преобразува в rvalue, което определено би било UB.

През 2000 г. имаше проблем с CWG към изяснете дали индиректността през null е недефинирана. Предложената резолюция (2004), която би изяснила, че индиректността чрез null е не UB, изглежда не е добавена към стандарта досега.

Но дали е или не е UB, няма голямо значение, тъй като не е необходимо да правите това. Най-малкото полученият указател ще бъде невалиден и следователно безполезен.

Ако сте планирали да преобразувате указателя в цяло число, за да получите отместването на члена, няма нужда да правите това, защото вместо това можете да използвате макроса offsetof от стандартната библиотека, която няма UB.


&(p[1]); // undefined?

Тук поведението е съвсем ясно недефинирано:

[expr.sub] ... Изразът E1[E2] е идентичен (по дефиниция) на *((E1)+(E2)), с изключение на това, че в случай на операнд от масив, резултатът е lvalue, ако този операнд е lvalue и xvalue в противен случай.

[expr.add] Когато израз J, който има интегрален тип, се добави или извади от израз P от тип указател, резултатът има тип P.

  • Ако P се оценява на нулева стойност на указателя и J се оценява на 0 (не се прилага)

  • В противен случай, ако P сочи към елемент от масив (не се прилага)

  • В противен случай поведението е недефинирано.


&(p[0]); // undefined?

Съгласно предходните правила се прилага първата опция:

Ако P се изчисли като стойност на нулев указател и J се изчисли на 0, резултатът е стойност на нулев указател.

И сега се връщаме към въпроса дали индиректността през тази нула е UB. Вижте началото на отговора.

Все пак няма особено значение. Няма нужда да пишете това, тъй като това е просто ненужно сложен начин за писане на sizeof(int) * i (като i е съответно 1 и 0).

person eerorika    schedule 12.06.2020
comment
Това може да се тълкува, че да, поведението е недефинирано. -- Не виждам друго възможно тълкуване. Подобно тълкуване би трябвало да обясни към кой обект сочи изразът - person M.M; 12.06.2020
comment
@M.M Честно. Премахнах това. - person eerorika; 12.06.2020