2016-12-10 3 views
2

После прочтения this, я понимаю, что следующая программа должна вызывать UB. Я прав?Неопределенное поведение?

int main(void) 
{ 
     char *ptr = "ABCD"; 
     ptr = 'A'; 
     printf("%c\n", ptr); 
} 

Спасибо.

+2

_Да, вы правы. Но, вероятно, не было необходимости задавать вопрос для этого, поскольку есть много подобных вопросов и ответов с объяснением. – ameyCU

+0

Отказался от интервью, сказав, что это UB. Я чувствовал, что интервьюер никогда не слышал, что такое УБ. В любом случае, удалит этот пост. – babon

+1

@PhilipCouling На моей машине нет ошибок. Просто предупреждение о возможном некорректном преобразовании указателя. Я использую GCC 6.2.1. Не могли бы вы объяснить больше? – babon

ответ

3

Вы правы, опубликованный код вызывает неоднозначное поведение несколькими способами.

Возможно, вы не хотите работать в компании, где такой код считается ОК.

Когда я компилировать код с моими опциями по умолчанию clang, я получаю 4 предупреждения:

clang -O2 -funsigned-char -std=c11 -Weverything -Wno-padded -Wno-shorten-64-to-32 -Wno-mis\ 
sing-prototypes -Wno-vla -Wno-missing-noreturn -Wno-sign-conversion -Wno-missing-variable-\ 
declarations -Wno-unused-parameter -Wwrite-strings -lm -o ub2 ub2.c 
ub2.c:3:11: warning: initializing 'char *' with an expression of type 'const char [5]' dis\ 
cards qualifiers [-Wincompatible-pointer-types-discards-qualifiers] 
    char *ptr = "ABCD"; 
     ^ ~~~~~~ 
ub2.c:4:9: warning: incompatible integer to pointer conversion assigning to 'char *' from \ 
'int' [-Wint-conversion] 
    ptr = 'A'; 
     ^~~~ 
ub2.c:5:5: warning: implicitly declaring library function 'printf' with type 'int (const c\ 
har *, ...)' 
    printf("%c\n", ptr); 
    ^
ub2.c:5:5: note: include the header <stdio.h> or explicitly provide a declaration for 'pri\ 
ntf' 
ub2.c:5:20: warning: format specifies type 'int' but the argument has type 'char *' [-Wfor\ 
mat] 
    printf("%c\n", ptr); 
      ~~  ^~~ 
      %s 
4 warnings generated. 

3 из этих предупреждений указывают на возможное неопределенное поведение:

  • назначая целое значение указателю 'A' вызывает неопределенное поведение, за исключением целочисленной константы 0, которая преобразуется в нулевой указатель. Некоторые системы будут инициировать исключение для некоторых значений, некоторые просто сохранят целочисленное значение в местоположении указателя, на который нельзя положиться. вы увидите код ptr = (char*)0x0040006C;, который может правильно компилироваться в конкретной системе, для которой он настроен, но он работает только для конкретных комбинаций компилятора/цели.

  • Передача указателя для спецификатора преобразования %c явно описывается как вызов неопределенного поведения в стандарте C.Указатели могут быть переданы в printf иначе, чем int значений, например, в другом наборе регистров, поэтому printf не получит значение указателя для печати в качестве символа. Это значение не может быть 'A' в любом случае, даже если правильно откинуто как (int)ptr.

  • , вызывающий printf без соответствующего объявления в области, вызывает неопределенное поведение. Неявный прототип, выведенный компилятором из переданных аргументов, может быть несовместимым с конвенцией о вызовах varargs, используемой printf. Вы должны указать <stdio.h> или хотя бы предоставить действительный прототип перед вызовом printf.

В меньшем примечании, есть еще несколько замечаний:

  • "ABCD" является строковым. Его нельзя писать. Для совместимости с тоннами устаревшего кода стандарт C (relunctantly) дает ему тип char[5], где он действительно должен быть const char[5]. Это объясняет, почему вы не получаете предупреждение по умолчанию на char *ptr = "ABCD";, но разумно разрешить компилятору быть более строгим, чем стандарт, и предупредить программиста об этом. const правильность может потребовать значительных изменений в крупных проектах, но предотвратит потенциальное неопределенное поведение и улучшит способность компиляторов оптимизировать код.

  • возвращение 0 из main() подразумевается, так как C99, но считается COOD стиль иметь явное return 0;, чтобы указать успех.

+0

Мне нравится, когда компилятор говорит «Что такое printf? не включайте прототип, но имейте в виду, что вы используете его плохо, потому что на самом деле я знаю, что такое printf " – Stargateur

1

c-string - это серия символов. Завершить с помощью char '\0'.

char *ptr = "ABCD"; 

Создать массив из 5 символов. {'A', 'B', 'C', 'D', '\0'} где-то в статической памяти и назначить адрес первого символа (ака. 'A') на ptr.

Когда вы

ptr = 'A'; 

присваивается значение из 'A' в ptr, это не является допустимым указателем или с-строка так de-referencing указатель причина неопределенное поведение.

Плюс, C - это типизированный язык, в который вы ничего не можете поместить. Это не гарантирует, что после назначения 'A' на ptr. То, что ptr будет равно 'A'. ptr имеет тип char *, 'A' имеет тип char. Вы не можете смешивать эти типы.

char *ptr = 'A'; 
if (ptr == 'A') // this is undefined behavior 
    *ptr; // same here 

Здесь вы должны написать, если хотите, делать то, что вы пытаетесь.

char c = 'A'; 
char *ptr = &c; // here ptr is not a valid c-string, it's just a pointer for one char 

printf("%c", *ptr); 
+1

Но указатель ** не ** разыменовывается. –

+0

Даже если @WeatherVane верен, на некоторых реализациях просто установка указателя на недопустимый адрес может вызвать UB. –

+0

@WeatherVane Это лучше сейчас? Я думаю, что это было наиболее важно объяснить, потому что держать недопустимый указатель действительно очень плохо. – Stargateur

-1

Здесь ptr указывает на местоположение в разделе раздела данных (только для чтения) программы. Вы модифицировали ptr = 'A'; Здесь ptr указывает на значение ascii i.e 65.So ptr указывает на значение в точке 65, следовательно, неопределенное поведение