2012-04-15 2 views
1

Почему 1, 2 и 3 работают, когда 4 генерирует ошибку сегментации? (См. Ниже)символов против массивов в c

char c[10]; 
char* d; 

1.

scanf("%s", &c); 
printf("%s\n", &c); 

2.

scanf("%s", c); 
printf("%s\n", c); 

3.

scanf("%s", &d); 
printf("%s\n", &d); 

4.

scanf("%s", d); 
printf("%s\n", d); 
+3

Как вы думаете? Прямо сейчас, похоже, вы пытаетесь заставить нас сделать домашнее задание для вас. – templatetypedef

+0

Отмечено как домашнее задание; если я ошибаюсь, не стесняйтесь удалить тег. – mc10

+0

Это не домашнее задание. Я просто готовлюсь к собеседованию и столкнулся с этой проблемой, пытаясь создать программу, которая генерирует перестановки. – Ivan

ответ

4

Повторяя код в вопросе:

char c[10]; 
char* d; 

1.

scanf("%s", &c); 
printf("%s\n", &c); 

Это, вероятно, работать, как ожидалось, но на самом деле поведение не определено.

scanf с форматом "%s" требует аргумента типа char*. &c имеет тип char (*)[10], то есть это указатель на массив char[10]. Он указывает на то же место в памяти, что и адрес 0-го элемента c, но он имеет другой тип. То же самое происходит с printf: формат "%s" подсказывает, что он ожидает аргумент char*, но вы передаете ему аргумент char(*)[10].

С scanf является вариационной функцией, нет необходимости проверять тип аргументов, отличных от строки формата. Компилятор (вероятно) с радостью передаст значение char (*)[10] в scanf, предполагая, что он может справиться с этим. И это возможно может, при реализации, где все указатели имеют такой же размер, представление и механизм передачи аргументов. Но, например, компилятор C для экзотической архитектуры может легко сделать указатели char* больше, чем указатели на более крупные типы. Представьте себе процессор, чей собственный адрес указывает, скажем, на 64-битное слово; a char* Указатель может быть составлен из указателя слов плюс смещение байта.

2.

scanf("%s", c); 
printf("%s\n", c); 

Это лучше. c - это массив, но в этом контексте выражение массива «распадается» на указатель на первый элемент массива - это именно то, что требуется scanf с форматом "%s". То же самое происходит, проходя c до printf. (Но есть еще какие-то проблемы, я вернусь к этому после того, как в других примерах

3.

scanf("%s", &d); 
printf("%s\n", &d); 

Поскольку d является единственным char* аргумент, &d имеет тип char**, и снова вы. Если все указатели имеют одинаковое представление (и тот же механизм передачи аргументов), а вход для scanf достаточно короткий, это может случиться с «работой». если это массив из char. Если char* - 4 байта, а входная строка больше не равна t han длиной 3 символа, это, вероятно, будет работать - как если бы вы использовали char[4] и правильно написали звонки. Но это чрезвычайно плохой практикой для хранения символьных строк непосредственно в объект-указатель, и существует огромный риск записи за конец объекта с непредсказуемыми результатами. (Эти непредсказуемые результаты включают записи в память, которая не используется ни для чего другого, что может появиться работать;. Такова природа неопределенного поведения)

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

4.

scanf("%s", d); 
printf("%s\n", d); 

Здесь типы все правильно, но если вы не инициализируется d, чтобы указать на достаточно большом массиве char, это, вероятно, не в состоянии эффектно (или, что еще хуже, по всей видимости, работать «правильно», что означает, у вас есть тонкая ошибка, которая, вероятно, появится позже).

И теперь мы добираемся до того, что я упоминал выше о других проблемах.

Например, 4, я упомянул, что d должен указывать на «достаточно большой» массив. Насколько велика «достаточно большая»? На это нет ответа. scanf("%s", ...) читает последовательность символов, разделенных пробелами, без верхней границы по ее длине. Например, если я запускаю вашу программу и удерживаю клавишу x, я могу предоставить строку ввода дольше, чем любой предоставленный вами буфер, с непредсказуемыми результатами (неопределенное поведение снова).

формата scanf функционального направления "%s"не может безопасно использоваться (если ваша программа не работает в среде, где вы можете контролировать то, что будет отображаться на стандартном потоке ввода).

Один хороший способ читать ввод текста - использовать fgets для чтения строки за раз, а затем использовать другие функции для анализа результата. fgets требует указать максимальную длину ввода; если фактический ввод превышает предел, он усечен и остается прочитанным более поздними вызовами. Это не так удобно, как scanf, но это можно сделать безопасно. (И никогда не использовать функцию gets, как scanf("%s", ...), он не может быть использован безопасно.)

Предложенное чтение:

Раздел 6 comp.lang.c FAQ делает отличную работу объяснения массивов C и указателей, и как они Связанные (и не связанные). В разделе 12 обсуждается стандартный ввод-вывод C.

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

3

Вы получили неопределенное поведение в случаях 3 и 4.

  • Случаи один и два одинаковы, поскольку оба указывает на первый элемент массива.
  • Случай 3 не определен, поскольку вы указываете указатель на символ при ожидании указателя на символ.
  • Дело 4 не определено, поскольку указатель d не инициализирован.

+0

Не делает ли случай 1 указатель на указатель на первый элемент в массиве (т. Е. Char **) вместо char *? – Ivan

+1

№ адреса массива ('& array') - это адрес первого элемента. Когда вы просто пишете ('array'), он распадается на указатель. оба действительны. – MByD

+1

@BinyaminSharet: Нет, оба недействительны. '& array' - это адрес всего массива. Он ссылается на тот же адрес памяти, что и '& array [0]', но он имеет другой тип. Вероятно, он работает в системе, где все указатели имеют одинаковое представление, но поведение не определено. –

2

3 работы (на многих платформах, а также с предупреждением, если вы включите те на, технически это неопределенное поведение), потому что вы злоупотребляя указатель (лечащий &d, который имеет тип (char **), как (char *) и сохранение символов внутри памяти, предназначенной для указателя). 4 умирает, потому что неинициализированный указатель указывает на случайный адрес.

0

Важным вопросом здесь является наличие пространства для хранения результата.

scanf("%s", &c); 
printf("%s\n", &c); 

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

scanf("%s", c); 
printf("%s\n", c); 

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

scanf("%s", &d); 
printf("%s\n", &d); 

Есть ли хранение? Да. Он не относится к соответствующему типу (char **, должен быть char *), но он не должен отличаться от того, чтобы отличить char в тип указателя и хранить его в переменной, объявленной как указатель. (Другие ответы говорят, что это неопределенное поведение. Я не думаю, что это так, отличное от char или любого другого целочисленного типа до char * или другого типа указателя, если это не рекомендуется, покажите мне, где стандарт говорит, что это undefined.)

scanf("%s", d); 
printf("%s\n", d); 

Есть ли хранение? Не то, что вы выделили. Технически может быть так, что все, что бы ни случилось в d, указывает на место в памяти, которое не будет segfault. Даже если это произойдет, это не ваша память, и вы можете переписать что-то важное или это может измениться неожиданно. Вы не сказали d, где найти действительную память, чтобы указать на нее, поэтому вы играете указатель русской рулетки.

+0

Первый пример имеет неопределенное поведение, потому что 'scanf' требует аргумент' char * ', но ему дается' char (*) [10] '. –

+0

@KeithThompson И это не гарантируется, что он рухнет на адрес первого элемента? Есть ли компиляторы там, где нет? – Kevin

+0

Нет никакого неявного преобразования из 'char (*) [10]' в 'char *'; это гарантировано * не * «свернуть» по адресу первого элемента. Вероятно, это сработает, если оба типа указателей имеют одинаковое поведение по размеру, представлению и аргументированию (хотя оптимизирующий компилятор все равно может его разбить), но поведение по-прежнему не определено. Попробуйте выполнить компиляцию кода с помощью gcc.Он дает предупреждение. –

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