2013-04-11 1 views
5

Я понимаю, что переменная char может принимать символ null (1 байт) i.e; \0 как его значение, но я не понимаю, как переменная char в моем приложении ниже принимает указатель (4 байта) в качестве его значения и все еще работает правильно?Как переменная char может принимать указатель (NULL) в качестве значения?

#include<stdio.h> 
int main() 
{ 
    char p[10]="Its C"; 
    printf("%s\n",p); 
    p[3]='\0';    // assigning null character 
    printf("%s\n",p); 
    p[2]=NULL;    // assigning null pointer to a char variable 
    printf("%s\n",p); 
    p[1]=(void *)0;   // assigning null pointer to a char variable 
    printf("%s\n",p); 
    return 0; 
} 

Примечание: GCC компилятора (32 Bit Linux Platform).

+0

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

+1

@Grijesh Chauhan Спасибо за редактирование – Neeraj

+0

@KeithThompson, Это был онлайн-редактор, который не выдавал никаких предупреждений. – Neeraj

ответ

9

Макрос NULL требуется для раскрытия до «константы нулевой указатель, определенной реализацией».

A константа нулевой указатель определяется как «целочисленное постоянное выражение со значением 0 или такое выражение, отлитое от типа void *». В отличие от этого определения не требует, чтобы расширение NULL являлось выражением типа указателя. Общая реализация является:

#define NULL 0 

Нулевой константный указатель, когда он используется в контексте, который требует указатель может быть неявно преобразуется в значение указателя; результатом является нулевая указатель .Он также может быть явно преобразован с использованием литого, например (int*)NULL.

Но нет требования, чтобы выражение, которое квалифицируется как константа нулевого указателя, может использоваться только в таком контексте. Это означает, что если реализация выбирает определить NULL, как указано выше, то это:

char c = NULL; // legal but ugly 

является законным и инициализирует c в нуль символов.

Такая инициализация не является портативным (так как NULL может также расширить до ((void*)0) и вводит в заблуждение, поэтому его следует избегать, но компилятор может позволить его через без предупреждения; NULL расширяется до 0 по фазе предварительной обработки компилятор, и более поздние фазы не рассматривают его как char c = 0;, который является законным и безобидным - хотя лично я предпочел бы char c = '\0';

Я просто попробовал ваш пример на моей 32-битной систему Ubuntu с GCC 4.7 при отсутствии.. опционы, компилятор предупреждал обо всех p[2]=NULL; и p[1]=(void *)0;:

c.c:8:9: warning: assignment makes integer from pointer without a cast [enabled by default] 
c.c:10:9: warning: assignment makes integer from pointer without a cast [enabled by default] 

Второе предупреждение ожидается от любого компилятора C; первый указывает, что NULL фактически определен как ((void*)0) (код запуска через gcc -E подтверждает это).

Компилятор не просто «принял» эти задания; он предупредил вас о них. Стандарт языка C просто требует «диагностики» для любого нарушения языковых правил, даже синтаксической ошибки; эта диагностика может на законных основаниях быть нефатальным предупреждающим сообщением. Вы можете сделать gcc более строго с -std=c89 -pedantic-errors; замените c89 на c99 или c11 для обеспечения соблюдения правил из более поздних версий стандарта. (EDIT: Я вижу из комментариев, что вы используете веб-интерфейс для компилятора, который скрывает предупреждения, см. Мой комментарий к вашему вопросу об обходном пути. Предупреждения важны.)

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

Язык-адвокат каламбур: это даже не ясно, что это:

char c = (void*)0; 

определяет преобразование из void* в char. Мое собственное мнение состоит в том, что, поскольку оно нарушает ограничение, оно не имеет определенной семантики. Большинство компиляторов, которые не отклоняют его, будут относиться к нему так, как если бы это было преобразование void* -to- char, и также утверждалось, что это необходимое поведение. Но вы можете избежать таких вопросов, если просто обратите внимание на предупреждения компилятора и/или не пишете такой код в первую очередь.

(правила немного отличаются для C++, но вы спрашиваете о C, так что я не буду вдаваться в это.)

+0

Это просто отличный ответ! – Fingolfin

+1

Спасибо! Это избавляет меня от необходимости писать новый ответ, предупреждая о возможном нежелательном поведении, которое является преобразованием указателя в целое, когда NULL определяется как «((void *) 0)» или какой-либо другой нулевой указатель. По-прежнему может быть полезно указать из стандарта: * Любой тип указателя может быть преобразован в целочисленный тип. За исключением случаев, указанных ранее, результат определяется реализацией. Если результат не может быть представлен в целочисленном типе, то это не определено. . Результат не должен быть в диапазоне значений любого целого числа . * – Sebivor

+1

Спасибо :) ... Я также хотел бы прочитать ваш другой пост в свободное время –

1

Попробуйте скомпилировать это с помощью опции -Wall, и вы увидите, что происходят невольные преобразования.

+0

Компиляция * без * '-Wall' выдает предупреждения. –

2

Потому что в компиляторах NULL подставляется под 0 в некоторых компиляторах и ((void*)0) в других.

Значение 0 сам по себе является допустимым значением для char, но с переходом к (void*), вы технически заливке 0 в тип указателя, следовательно, почему компилятор выдаст предупреждение. Обратите внимание, что если компилятор заменяет NULL с 0, целочисленной константой, он будет просто и бесшумно преобразован в char.

+1

Немного пояснил – Fingolfin

+0

Я провожу меня как нисходящего. –

+1

@ JohannesSchaub-litb: Могу ли я узнать почему? Я каким-то образом отредактировал ответ, чтобы понять, что я имею в виду. – Fingolfin

2

NULL макрос и почти платформы определяется таким образом

#ifndef __cplusplus 
#define NULL ((void *)0) 
#else /* C++ */ 
#define NULL 0 
#endif /* C++ */ 

(от stddef.h из моей Ubuntu)

и когда вы пишете

p[2]=NULL; 

Это же

p[2]=(void *)0; //for c 
p[2]=0; //for c++ 

Это тот же

p[2] = 0; // the 0 is casted to char 0 for C --> '\0' 
+0

@GrijeshChauhan fixed – MOHAMED

+0

Я заметил, что вопрос задан только 'c', поэтому вы должны получить + :) :) и + показать разницу в c и C++ good –

2

на вашей платформе, указатель, как правило, числовое значение рассматривается как адрес памяти. Поскольку char тип числовой, пустой указатель (адрес памяти 0x00) в настоящее время хранится в p[1]

32-битное значение указателя (в данном случае, 0x00000000) усекается до 8-битового char длина: 0x00.

+1

Нет, указатели - это не просто числовые значения; что касается языка, то это совершенно разные сущности. Если 'NULL' расширяется до' ((void *) 0) ', назначение является нарушением ограничения, требующим хотя бы предупреждения (нет никакого неявного преобразования из' void * 'в' char'). Если 'NULL' расширяется до' 0', конверсия указателя отсутствует; 'NULL' расширяется до постоянного выражения типа' int', которое неявно преобразуется в 'char'. –

+3

@ KeithThompson - OP явно заявил, что использует GCC на 32-битной платформе Linux. На этой платформе указатели * являются * числовыми адресами памяти. Не верьте мне? Сделайте дамп памяти :) – Unsigned

+0

Учитывая добавленную квалификацию «На вашей платформе», это немного лучше. Но нет преобразования из 'void *' в 'intptr_t', так как в коде нет упоминания' inptr_t', явного или неявного. Присвоение значения 'void *' символу 'char' является нарушением ограничения; предполагая, что компилятор просто предупреждает об этом, он * вероятно * приведет к преобразованию (* not * a cast, который является явным оператором) из 'void *' в 'char'. В любом случае, IMHO лучше думать о указателях как отличных от целых чисел; то, как они представлены внутри, является детальностью реализации. –

 Смежные вопросы

  • Нет связанных вопросов^_^