Можно ли инициализировать C-указатель NULL?

Я писал такие вещи, как

char *x=NULL;

в предположении, что

 char *x=2;

создаст указатель char для адреса 2.

Но, в В учебнике по программированию GNU C, в нем указано, что int *my_int_ptr = 2; хранит целочисленное значение 2 для любого случайного адреса в my_int_ptr, когда он выделен.

Казалось бы, это означает, что мой собственный char *x=NULL присваивает то, что значение NULL, переданное в char, относится к некоторому случайному адресу в памяти.

В то время как

#include <stdlib.h>
#include <stdio.h>

int main()
{
    char *x=NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

действительно делает печать

имеет значение NULL

когда я компилирую и запускаю его, я обеспокоен тем, что я полагаюсь на поведение undefined или, по крайней мере, недоопределенное поведение, и что я должен написать

char *x;
x=NULL;

вместо.

Ответ 1

Можно ли инициализировать указатель C NULL?

TL; DR Да, очень.


фактическое требование, сделанное в руководстве, читается как

С другой стороны, если вы используете только одно начальное назначение, int *my_int_ptr = 2;, программа попытается заполнить содержимое ячейки памяти, на которую указывает my_int_ptr, со значением 2. Поскольку my_int_ptr заполняется с мусором, это может быть любой адрес. [...]

Ну, они являются неправильными, вы правы.

Для оператора (игнорируя на данный момент тот факт, что указатель на целочисленное преобразование является поведением, определяемым реализацией)

int * my_int_ptr = 2;

my_int_ptr - это переменная (указателя типа на int), у нее есть собственный адрес (тип: адрес указателя на целое число), вы сохраняете значение 2 на этот адрес.

Теперь my_int_ptr, являясь типом указателя, мы можем сказать, что он указывает на значение "type" в ячейке памяти, на которое указывает значение, хранящееся в my_int_ptr. Таким образом, вы по существу присваиваете значение переменной указателя, а не значение ячейки памяти, на которую указывает указатель.

Итак, для заключения

 char *x=NULL;

инициализирует переменную-указатель x до NULL, а не значение в адресе памяти, на которое указывает указатель.

Это тот же, что и

 char *x;
 x = NULL;    

Расширение:

Теперь, строго говоря, утверждение типа

 int * my_int_ptr = 2;

является незаконным, поскольку он связан с нарушением ограничений. Чтобы быть ясным,

  • my_int_ptr - указательная переменная, введите int *
  • целочисленная константа, 2 имеет тип int, по определению.

и они не являются "совместимыми" типами, поэтому эта инициализация недействительна, поскольку она нарушает правила простого назначения, упомянутые в главе §6.5.16.1/P1, описанные в Lundin answer.

В случае, если кому-то интересно, как инициализация связана с простыми ограничениями назначения, цитируя C11, главу §6.7.9, P11

Инициализатор для скаляра должен быть одним выражением, необязательно заключенным в фигурные скобки. начальное значение объекта - это выражение (после преобразования); того же типа ограничения и преобразования, как для простого присваивания, принимая тип скаляра быть неквалифицированной версией его объявленного типа.

Ответ 2

Учебник ошибочен. В ISO C ошибка int *my_int_ptr = 2;. В GNU C это означает то же, что и int *my_int_ptr = (int *)2;. Это преобразует целое число 2 в адрес памяти, в некотором роде, как определено компилятором.

Он не пытается хранить что-либо в адресе, адресуемом этим адресом (если есть). Если вы продолжаете писать *my_int_ptr = 5;, тогда он попытается сохранить номер 5 в адресе, адресуемом этим адресом.

Ответ 3

Чтобы пояснить, почему учебник ошибочен, int *my_int_ptr = 2; является "нарушением ограничения", это код, который не разрешается компилировать, и компилятор должен дать вам диагностику, столкнувшись с ней.

Согласно 6.5.16.1 Простое назначение:

Ограничения

Одно из следующего:

  • левый операнд имеет атомный, квалифицированный или неквалифицированный арифметический тип, а правый имеет арифметический тип;
  • левый операнд имеет атомную, квалифицированную или неквалифицированную версию структуры или типа объединения, совместимую с типом права;
  • левый операнд имеет атомный, квалифицированный или неквалифицированный тип указателя и (учитывая тип, который левый операнд имел бы после lvalue преобразование) оба операнда являются указателями на квалифицированные или неквалифицированные версии совместимых типов, а тип, на который указывает левый, все квалификаторы типа, на которые указывает справа;
  • левый операнд имеет атомный, квалифицированный или неквалифицированный тип указателя и (учитывая тип, который левый операнд имел бы после lvalue преобразование) один операнд является указателем на тип объекта, а другой является указателем на квалифицированную или неквалифицированную версию void, а тип, на который указывают левые, имеет все определители типа, указанного по праву;
  • левый операнд является атомарным, квалифицированным или неквалифицированным указателем, а справа - константа нулевого указателя; или
  • левый операнд имеет тип атомный, квалифицированный или неквалифицированный _Bool, а правый - указатель.

В этом случае левый операнд является неквалифицированным указателем. Нигде не упоминается, что правильному операнду разрешено быть целым числом (арифметический тип). Таким образом, код нарушает стандарт C.

Известно, что GCC плохо себя ведет, если вы явно не говорите, что это стандартный компилятор C. Если вы скомпилируете код как -std=c11 -pedantic-errors, он будет правильно давать диагностику, как она должна быть.

Ответ 4

int *my_int_ptr = 2

хранит целочисленное значение 2 для любого случайного адреса в my_int_ptr, когда он выделен.

Это совершенно неправильно. Если это на самом деле написано, пожалуйста, получите лучшую книгу или учебник.

int *my_int_ptr = 2 определяет целочисленный указатель, указывающий на адрес 2. Скорее всего, вы получите сбой, если попытаетесь получить доступ к адресу 2.

*my_int_ptr = 2, то есть без int в строке, сохраняет значение два, к которому указывает любой случайный адрес my_int_ptr. Сказав это, вы можете назначить NULL указателю, когда оно определено. char *x=NULL; отлично действует C.

Изменить: при написании этого я не знал, что целочисленное преобразование указателя является реализацией определенного поведения. Пожалуйста, см. Хорошие ответы от @M.M и @SouravGhosh для деталей.

Ответ 5

Много путаницы в отношении указателей C происходит из очень плохого выбора, который был первоначально сделан в отношении стиля кодирования, подтвержденного очень плохим выбором в синтаксисе языка.

int *x = NULL; правильный C, но он очень вводит в заблуждение, я даже сказал бы бессмысленно, и это мешало пониманию языка для многих новичков. Это заставляет думать, что позже мы могли бы сделать *x = NULL;, что, конечно, невозможно. Видите ли, тип переменной не является int, а имя переменной не является *x, а * в объявлении не играет никакой функциональной роли в сотрудничестве с =. Это чисто декларативный. Итак, что еще более важно:

int* x = NULL;, который также является правильным C, хотя он не придерживается оригинального стиля кодирования K & R. Совершенно ясно, что тип int*, а указательная переменная x, поэтому становится очевидным даже для непосвященных, что значение NULL хранится в x, что является указателем на int.

Кроме того, упрощается вывод правила: когда звезда находится вдали от имени переменной, это объявление, а звездочка, прикрепленная к имени, является разыменованием указателя.

Итак, теперь становится намного понятнее, что в дальнейшем мы можем либо делать x = NULL;, либо *x = 2;, другими словами, это облегчает начинающему, чтобы увидеть, как variable = expression приводит к pointer-type variable = pointer-expression и dereferenced-pointer-variable = expression. (Для инициированного, по выражению "я имею в виду" rvalue ".)

Несчастный выбор в синтаксисе языка заключается в том, что при объявлении локальных переменных вы можете сказать int i, *p;, который объявляет целое число и указатель на целое число, поэтому он приводит к тому, что * является полезной частью от имени. Но это не так, и этот синтаксис - просто причудливый особый случай, добавленный для удобства, и, на мой взгляд, он никогда не существовал бы, потому что он недействительно правила, которое я предложил выше. Насколько я знаю, нигде в языке этот синтаксис не имеет смысла, но даже если это так, он указывает на несоответствие способа определения типов указателей в C. В другом месте, в объявлениях с одной переменной, в списках параметров, в членах структуры и т.д. вы можете объявить свои указатели как type* pointer-variable вместо type *pointer-variable; это совершенно законно и имеет больше смысла.

Ответ 6

Я хотел бы добавить что-то, ортогональное многим отличным ответам. На самом деле инициализация до NULL далека от плохой практики и может быть полезна, если этот указатель может или не может использоваться для хранения динамически выделенного блока памяти.

int * p = NULL;
...
if (...) {
    p = (int*) malloc(...);
    ...
}
...
free(p);

Так как согласно стандарт ISO-IEC 9899 free является nop, когда аргумент NULL, код выше (или что-то более значимое по тем же направлениям) является законным.

Ответ 7

это нулевой указатель

int * nullPtr = (void*) 0;

Ответ 8

Это правильно.

int main()
{
    char * x = NULL;

    if (x==NULL)
        printf("is NULL\n");

    return EXIT_SUCCESS;
}

Эта функция правильна для того, что она делает. Он присваивает адрес 0 указателю char x. То есть, он указывает на указатель x на адрес памяти 0.

Альтернатива:

int main()
{
    char* x = 0;

    if ( !x )
        printf(" x points to NULL\n");

    return EXIT_SUCCESS;
}

Мое предположение о том, что вы хотели:

int main()
{
    char* x = NULL;
    x = alloc( sizeof( char ));
    *x = '2';

    if ( *x == '2' )
        printf(" x points to an address/location that contains a '2' \n");

    return EXIT_SUCCESS;
}

x is the street address of a house. *x examines the contents of that house.

Ответ 9

Просто помните:

В C значение слева всегда оценивается в области памяти, тогда как правая сторона всегда оценивает значение (будь то int или адрес или класс Foo).