Дали (int *)0 е нулев указател?

Това може да се разглежда като разширение на този въпрос (Интересувам се само от C, но добавяне на C++ за завършване на разширение)

Стандартът C11 в 6.3.2.3.3 казва:

Целочислен постоянен израз със стойност 0 или такъв израз, преобразуван към тип void *, се нарича константа с нулев указател.

Това, което лично аз смятам за това, е, че 0 и (void *)0 представляват нулев указател, чиято целочислена стойност може всъщност да не е 0, но това не покрива 0, прехвърлено към друг тип.

Но след това стандартът продължава:

Ако константа нулев указател се преобразува в тип указател, полученият указател, наречен нулев указател,...

което обхваща (int *)0 като нулев указател, тъй като cast е изрично преобразуване (C11, 6.3), което е посочено в методите за преобразуване.

Но това, което все още ме кара да се чудя, е следната фраза

... или такъв израз, преобразуван в тип void * ...

С горната семантика тази фраза изглежда напълно безполезна. Въпросът е дали тази фраза е напълно безполезна? Ако не, какви последици има? Следователно, (int *)0 нулевият указател ли е или не?


Друг въпрос, който може да помогне на дискусията е следният. Дали (long long)123 се счита за "123 преобразувано в long long", или за "123 с тип long long". С други думи, има ли преобразуване в (long long)123? Ако няма такъв, тогава вторият цитат по-горе не покрива (int *)0 като нулев указател.


person Shahbaz    schedule 27.01.2014    source източник
comment
Може да искате да погледнете stackoverflow. com/questions/2597142/   -  person Colonel Thirty Two    schedule 27.01.2014
comment
(int*)0 е а нулев указател, а не   -  person David Rodríguez - dribeas    schedule 27.01.2014
comment
@DavidRodríguez-dribeas, прав си, но тъй като Всички два нулеви указателя ще се сравняват равни (C11, 6.3.2.3-4), всъщност няма да има голяма разлика в разграничаването между тях.   -  person Shahbaz    schedule 27.01.2014
comment
Може да искате да разгледате въпроса ми тук: stackoverflow.com/questions/3889541/   -  person Vicky    schedule 27.01.2014
comment
@Shahbaz: Е, всичко зависи. null int* същото ли е като null double*? Предполагам, че в C е така...   -  person David Rodríguez - dribeas    schedule 27.01.2014


Отговори (2)


Кратък отговор:

Както в C, така и в C++, (int *)0 е постоянен израз, чиято стойност е нулев указател. Това обаче не е константа с нулев указател. Единствената видима разлика между константен израз-чиято-стойност-е-нулев-указател и константа-нулев-указател, за която знам, е, че константата-нулев-указател може да бъде присвоена на l-стойност на всяка тип указател, но константен-израз-чиято-стойност-е-нулев-указател има специфичен тип указател и може да бъде присвоен само на lvalue със съвместим тип. В C, но не и в C++, (void *)0 също е константа с нулев указател; това е специален случай за void * в съответствие с общото правило за C-но не и C++, че void * е присвояване, съвместимо с всеки друг тип указател към обект.

Например:

long *a = 0;           // ok, 0 is a null pointer constant
long *b = (long *)0;   // ok, (long *)0 is a null pointer with appropriate type
long *c = (void *)0;   // ok in C, invalid conversion in C++
long *d = (int *)0;    // invalid conversion in both C and C++

И ето случай, в който разликата между константата нулев указател (void *)0 и константен израз-чиято-стойност-е-нулев указател с тип void * е видима дори в C:

typedef void (*fp)(void);  // any pointer-to-function type will show this effect

fp a = 0;                  // ok, null pointer constant
fp b = (void *)0;          // ok in C, invalid conversion in C++
fp c = (void *)(void *)0;  // invalid conversion in both C and C++

Освен това в днешно време това е спорно, но тъй като го повдигнахте: Без значение какво е битовото представяне на нулевия указател на long *, всички тези твърдения се държат, както е посочено от коментарите:

// 'x' is initialized to a null pointer
long *x = 0;

// 'y' is initialized to all-bits-zero, which may or may not be the
// representation of a null pointer; moreover, it might be a "trap
// representation", UB even to access
long *y;
memset(&y, 0, sizeof y);

assert (x == 0);         // must succeed
assert (x == (long *)0); // must succeed
assert (x == (void *)0); // must succeed in C, unspecified behavior in C++
assert (x == (int *)0);  // invalid comparison in both C and C++

assert (memcmp(&x, &y, sizeof y) == 0); // unspecified

assert (y == 0);         // UNDEFINED BEHAVIOR: y may be a trap representation
assert (y == x);         // UNDEFINED BEHAVIOR: y may be a trap representation

„Неуточнените“ сравнения не провокират недефинирано поведение, но стандартът не казва дали оценяват вярно или невярно и не се изисква внедряването да документира кое от двете е или дори да избере едно и да се придържа към него. Би било напълно валидно горното memcmp да редува връщането на 0 и 1, ако сте го извикали много пъти.


Дълъг отговор със стандартни кавички:

За да разберете какво е константа нулев указател, първо трябва да разберете какво е целочислен константен израз, а това е доста странно -- пълното разбиране изисква да прочетете раздели 6.5 и 6.6 от C99 в детайли. Това е моето резюме:

  • Константен израз е всеки C израз, който компилаторът може да изчисли като константа, без да знае стойността на който и да е обект (const или друго; обаче, enum стойностите са честна игра), и който няма странични ефекти. (Това е драстично опростяване на приблизително 25 страници стандартен текст и може да не е точно.)

  • #P10#
    #P11# #P12#
    #P13#
    #P14#
    #P15#

Дефиницията на C++98 изглежда повече или по-малко еквивалентна, модулни характеристики на C++ и отклонения от C. Например, по-силното разделяне на символни и булеви типове от цели числа в C++ означава, че стандартът C++ говори за "интеграл константни изрази“, а не „целочислени константни изрази“, и след това понякога изисква не просто интегрален константен израз, а интегрален константен израз от целочислен тип, с изключение на char, wchar_t и bool (а може и signed char и unsigned char? не ми става ясно от текста).

Сега, дефиницията на C99 за константа на нулев указател е това, за което се отнася този въпрос, така че ще го повторя: 6.3.2.3p3 казва

Цялочислен постоянен израз със стойност 0 или такъв израз, преобразуван към тип void *, се нарича константа с нулев указател. Ако константата на нулев указател се преобразува в тип указател, полученият указател, наречен нулев указател, е гарантирано, че ще се сравни неравно с указател към който и да е обект или функция.

Standardese е много, много буквален. Тези две изречения означават абсолютно същото като:

Целочислен постоянен израз със стойност 0 се нарича константа с нулев указател.
Целочислен постоянен израз със стойност 0, преобразуван към тип void *, е също < em>константа на нулев указател.
Когато която и да е константа на нулев указател се преобразува в тип указател, полученият указател се нарича нулев указател и е гарантиран да сравнявам неравностойно...

(Курсив - дефиниция на термина. Удебелен шрифт - моето ударение.) Това означава, че в C (long *)0 и (long *)(void *)0 са два начина за писане на абсолютно едно и също нещо, а именно нулевия указател с тип long *.

C++ е различен. Еквивалентният текст е C++98 4.10 [conv.ptr]:

Константа на нулев указател е интегрален константен израз (5.19) rvalue от целочислен тип, който се оценява на нула.

Това е всичко. „Интегрален константен израз rvalue от целочислен тип“ е почти същото нещо като „целочислен константен израз“ на C99, но има няколко неща, които отговарят на условията в C, но не и в C++: например в C символният литерал '\x00' е целочислена константа израз и следователно константа с нулев указател, но в C++ той не е интегрален константен израз от тип цяло число, така че също не е константа с нулев указател.

По-важно обаче е, че C++ няма клаузата "или подобен израз, преобразуван към void *". Това означава, че ((void *)0) не е константа с нулев указател в C++. Той все още е нулев указател, но не е присвояване, съвместимо с друг тип указател. Това е в съответствие с обикновено по-придирчивата система за типове на C++.

C++11 (но не, AFAIK, C11) ревизира концепцията за "нулев указател", добавяйки специален тип за тях (nullptr_t) и нова ключова дума, която оценява константа на нулев указател (nullptr). Не разбирам напълно промените и няма да се опитвам да ги обясня, но съм почти сигурен, че голото 0 все още е валидна константа на нулев указател в C++11.

person zwol    schedule 27.01.2014
comment
Ще съм благодарен, ако споменете за кой стандарт говорите, т.е. C или C++. Освен това можете ли да дадете пример за постоянен израз, който може да има (void *)0, но не и (int *)0? - person Shahbaz; 27.01.2014
comment
C, по-специално C99; Все още не съм чел C11 и не си спомням набързо дали (void *)0 все още е нулев указател константа в C++. Има има разлики между C и C++ в тази област, например NULL не може да се разшири до ((void *)0) в C++. - person zwol; 27.01.2014
comment
@Zack Както C, така и C++ изискват NULL да бъде константа с нулев указател. В C99 това включва (void *)0, но C++ дефинира константа с нулев указател като интегрален постоянен израз rvalue от целочислен тип, който се оценява на нула. - person D Krueger; 27.01.2014
comment
@DKrueger Не можах да си спомня дали C++ изключва ((void *)0) от дефиницията на константа с нулев указател или просто казва, че на NULL не е разрешено да се разширява до този израз. Изглежда, че беше първото. - person zwol; 28.01.2014
comment
Не съм сигурен защо fp c = (void *)(void *)0 е невалиден. Може ли някой да обясни? - person iBug; 22.11.2017
comment
@iBug Тъй като няма имплицитно преобразуване от void * към какъвто и да е тип указател към функция и двойното преобразуване към void * прави дясната страна на присвояването не константа на нулев указател (въпреки че той все още е константен израз, чиято стойност е нулев указател), така че имплицитното преобразуване на константите на нулев указател към всеки тип указател също не е достъпно. fp c = (fp)(void *)(void *)0 би бил валиден отново. - person zwol; 22.11.2017
comment
@zwol, само малка подробност. (void *)(void *)0 не е константа за нулев указател, но нещо като int *x = (void *)(void *)0; все още е добре. Това е просто обичайното присвояване от void * до any *, което е разрешено в C. Не съм проверил със стандарта, но gcc -std=c99 -pedantic всъщност отделя функционални указатели, където присвояването от void * е невалидно: предупреждение: ISO C забранява инициализацията между указателя на функцията и 'void *' [-Wpedantic]. Вашата гледна точка все още е вярна, просто исках да отбележа това. - person Shahbaz; 11.06.2018
comment
@Shahbaz Да, вижте stackoverflow.com/questions/47446430/ за куп допълнителни изложения по тази точка. - person zwol; 11.06.2018
comment
@zwol Господине, нямате ли конфликт между първия и третия пример. Първо казахте, long *d = (int *)0; // невалидно преобразуване както в C, така и в C++, но в трето assert (x == (int *)0); // трябва да успее в C, неуточнено в C++ ?? Ако не, защо? Бихте ли обяснили? - person snr; 31.03.2019
comment
@zwol и моят английски не е много добър като твоя, какво имаш предвид, като казваш капан и представяне на капан в този контекст? - person snr; 31.03.2019
comment
@snr Първи въпрос: Това е грешка, благодаря, че забеляза. Ще коригирам отговора. Втори въпрос: вижте stackoverflow.com/questions/6725809/trap-representation/6725981 . - person zwol; 31.03.2019

Изчисляването на израза (int*)0 дава нулев указател от тип int*.

(int*)0 не е константа с нулев указател.

Константата на нулев указател е определен вид израз, който може да се появи в изходния код на C. Нулев указател е стойност, която може да се появи в работеща програма.

C и C++ (като два различни езика) имат малко по-различни правила в тази област. C++ няма формулировката "или подобен израз, преобразуван в тип void*". Но не мисля, че това влияе на отговора на въпроса ви.

Що се отнася до въпроса ви за (long long)123, не съм сигурен как е свързан, но изразът 123 е от тип int, а кастингът указва преобразуване от int в long long.

Мисля, че основното объркване е предположение, че преобразуването в (int*)0 не указва преобразуване, тъй като 0 вече е константа на нулев указател. Но нулева константа на указател не е непременно израз на тип указател. По-специално, изразът 0 е както константа на нулев указател, така и израз от тип int; не е от никакъв тип указател. Терминът константа на нулев указател трябва да се разглежда като единична концепция, а не като фраза, чието значение зависи от отделните думи, които я съставят.

person Keith Thompson    schedule 27.01.2014
comment
Въпреки това (използвайки GCC-4.8.1/MinGW-32 под Windows 7) (int *)0 == NULL изглежда, че оценява на 1 (т.е. вярно). Така че сравняването е равно на константа с нулев указател. Това стандарт ли е или е дефинирано от внедряването? - person Kninnug; 27.01.2014
comment
Поправих случая на NULL. Но това ли е вашият отговор на маркер [language-lawyer] или трябва да очаквам 10 редакции на минута, преди да започна да чета отговора? - person Shahbaz; 27.01.2014
comment
@Kninnug Да, това е стандартно, но по силата на различен раздел от стандарта: дефиницията на == казва, че всички нулеви указатели се сравняват еднакво, независимо от техния точен тип. - person zwol; 27.01.2014
comment
@KeithThompson, съжалявам, видях отговор от два реда (който не е много точен и стандартно цитиран!) и си помислих, че може да си един от тях най-бързият пистолет на запад хора. - person Shahbaz; 27.01.2014
comment
@n.m., нямам нищо против това. Но дали (int *)0 е нулев указател? Кийт казва да, но не се дава препратка или аргументация с това. Може и просто да дава личното си мнение. - person Shahbaz; 27.01.2014
comment
@Shahbaz: Вие сам цитирахте съответната част от стандарта. 0 е константа с нулев указател. Преобразуването му във всеки тип указател дава нулев указател. - person Keith Thompson; 27.01.2014
comment
@KeithThompson, тук идва вторият въпрос. Ако (long long)123 е int, преобразуван в long long, тогава да, (int *)0 е нулев указател, преобразуван в int *. Но ако (long long)123 е просто long long число със стойност 123 (без преобразуване), тогава (int *)0 също ще бъде указател на int * със стойност 0. Аз самият търся това в стандарта. - person Shahbaz; 27.01.2014
comment
И в двата случая актьорският състав определя тривиално пладне преобразуване. Въпреки че 0 е константа с нулев указател, тя все още е от тип int. Няма int* със стойност 0, тъй като 0 не е от тип int*. - person Keith Thompson; 27.01.2014