C Strings: простой вопрос - PullRequest
6 голосов
/ 27 июня 2011

У меня есть три переменные, инициализированные ниже:

char c1[] = "Hello";
char c2[] = { 'H', 'e', 'l', 'l', 'o', '\0'};
char* c3 = "Hello";

Я знаю, что c1 и c2 одинаковы, и что они обе строки, потому что они заканчиваются на \ 0.Однако с3 отличается от с1 и с2.Это потому, что c3 не заканчивается на \ 0?Означает ли это, что c3 не строка?Если c3 не строка, то почему printf("%s", c3); не выдает ошибку?Спасибо!

РЕДАКТИРОВАТЬ:

Есть ли причина, почему c1 и c2 могут быть изменены, но c3 не может быть?

Ответы [ 9 ]

10 голосов
/ 27 июня 2011

С точки зрения C, наиболее существенным отличием между c3 и остальными является то, что вам не разрешается пытаться изменить базовые символы с помощью c3. Мне часто бывает полезно думать об этом так:

char *xyz = "xyz";

создаст модифицируемый указатель в стеке и сделает его указателем на неизменяемую последовательность символов {'x','y','z','\0'}. С другой стороны,

char xyz[] = "xyz";

создаст модифицируемый массив в стеке, достаточно большом для размещения последовательности символов {'x','y','z','\0'}, а затем скопирует в него эту последовательность символов. Содержимое массива будет изменяемым. Имейте в виду, что в стандарте ничего не говорится о стеках, но обычно так и делается. В конце концов, это просто память.

Формально c3 - это указатель на строковый литерал, тогда как c1 и c2 - это массивы символов, которые заканчиваются нулевым символом. Когда они передаются в функции, такие как printf, они распадаются на указатель на первый элемент массива, что означает, что они будут обрабатываться идентично c3 в этой функции (на самом деле они распадаются при довольно многих обстоятельствах, см. третья цитата из c99 ниже для исключений).

Соответствующие разделы C99: 6.4.5 String literals, что объясняет, почему вам не разрешено изменять то, на что c3 указывает:

Не определено, различаются ли эти массивы при условии, что их элементы имеют соответствующие значения. Если программа пытается изменить такой массив, поведение не определено.

и почему он имеет нулевой терминатор:

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

И 6.3.2.1 Lvalues, arrays, and function designators при 6.3 Conversions состояниях:

За исключением случаев, когда это операнд оператора sizeof или унарный оператор &, или строковый литерал, используемый для инициализации массива, выражение с типом '' массив типа '' преобразуется в выражение с типом ' указатель на тип, который указывает на начальный элемент объекта массива и не является lvalue. Если объект массива имеет класс хранения регистров, поведение не определено.

5 голосов
/ 27 июня 2011

Первая точка,

char* c3 = "Hello"; // may be valid C, but bad C++!

- это стиль, подверженный ошибкам, поэтому не используйте его. Вместо этого используйте

const char* c3 = "Hello";

Это действительный код. Указатель c3 указывает на адрес места, где хранится строка "Hello". Но вы не можете изменить *c3 (т.е. содержимое c3) как более ранние случаи (если вы это сделаете, это неопределенное поведение).

3 голосов
/ 27 июня 2011

c3 - указатель на строку, то есть то, что printf("%s", ...) ожидает в качестве аргумента.

Причина, по которой printf("%s", c1) или printf("%s", c2) также будет работать, заключается в том, что при затухании массивов Cуказатели очень легко в выражениях.Фактически, единственный раз, когда имя массива не превращается в указатель в выражении, это когда он используется в качестве операнда для оператора sizeof или операнда для оператора & (address-of).

Это приводит к распространенному заблуждению, что указатели и массивы эквивалентны в C, что не является правильным.Просто в C-массивах можно использовать почти везде указатели.Единственным исключением является то, что они не могут быть назначены, за исключением случаев, когда они подписаны (что является выражением, которое обрабатывает их как указатель).

Обратите внимание, что есть еще одно отличие в последней строке -так как это строковый литерал, его нельзя изменить (неизвестно, что произойдет, если вы попробуете).`

1 голос
/ 27 июня 2011

В C константа "string" может иметь два значения, в зависимости от контекста, в котором она используется. Он может обозначать либо строку в разделе ro исполняемого файла (хотя я не думаю, что это прописано в стандарте), делая const char *foo = "bar" оператором, инициализирующим foo, чтобы указать место в памяти загруженного исполняемого файла. Если двоичный двоичный объект ("bar") действительно находится в разделе ro, и вы делаете что-то вроде foo[0] = 'x', вы получите SIGSEGV.

Однако, когда вы пишете char x[] = "Hello" (или char x[6] = "Hello"), вы используете "Hello" в качестве инициализатора массива (например, int x[2] = { 1, 2 }), а x - это просто выделенный массив (доступный для записи) в стеке. В этом случае "Hello" является просто сокращением для {'H', 'e', 'l', 'l', 'o', '\0' }.

И "bar", и "Hello" завершены нулем.

1 голос
/ 27 июня 2011

c1 и c2 выделяют 6 байтов памяти и сохраняют в ней строку с нулевым символом в конце.

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

0 голосов
/ 27 июня 2011

C3 - указатель на первую ячейку в строке.C1, C2 - это обычный массив, на который никто не указывает.

0 голосов
/ 27 июня 2011

Указатель на строку с другим окончанием.

0 голосов
/ 27 июня 2011

Это строка Указывает на строку, но это рискованно.

0 голосов
/ 27 июня 2011

c3 не заканчивается NUL или NULL.Это указатель на строку, оканчивающуюся NUL.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...