Непонимание того, как char * s и char s [] работают на низком уровне - PullRequest
1 голос
/ 07 мая 2020

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

См. Следующие фрагменты -

char s[9] = "foobar";  //ok
s[1] = 'z'             //also ok

И

char s[9];
s = "foobar"   //doesn't work. Why? 

Но посмотрите следующие случаи -

char *s = "foobar";      //works
s[1] = 'z';              //doesn't work
char *s;
s = "foobar";            //unlike arrays, works here

Это немного сбивает с толку. Я имею в виду, что я смутно понимаю, что мы не можем присваивать значения массивам. Но мы можем это изменить. В случае char *s кажется, что мы можем присваивать значения, но не можем их изменять, потому что они записаны в постоянной памяти. Но все же я не могу получить полную картину.

Что именно происходит на низком уровне?

Ответы [ 4 ]

3 голосов
/ 07 мая 2020

char s[9] = "foobar"; Это инициализация . Объявляется массив символов размера 9, и затем его содержимое получает строку "foobar" с любыми оставшимися символами, установленными на '\0'.

s = "foobar" просто недопустимый C синтаксис . Вы не можете присвоить строку массиву символов. Чтобы s имел значение foobar. Используйте strcpy(s,"foobar");

char *s = "foobar"; также инициализацию, однако это присваивает адрес постоянной строки foobar переменной-указателю s. Обратите внимание, что я говорю «постоянная строка». На большинстве платформ строковый литерал является константой. Лучший способ прояснить это - написать const char *s = "foobar";

И действительно, ваше следующее задание s[1]= 'z'; не будет работать, потому что s является постоянным.

1 голос
/ 07 мая 2020

Это объявляет массив s с инициализатором :

char s[9] = "foobar";  //ok

Но это недопустимое выражение присваивание с массивом s слева:

s = "foobar";   //doesn't work. Why?

Выражения присваивания и объявления с инициализаторами синтаксически не одно и то же, хотя оба они используют = в своем синтаксисе.

Причина, по которой присваивание массиву s не работает, потому что массив распадается на указатель на свой первый элемент в выражении, поэтому присвоение эквивалентно:

&(s[0]) = "foobar";

Для выражения присваивания требуется lvalue на слева, но результат адресного оператора & не является lvalue . Хотя сам массив s является lvalue , выражение преобразует его во что-то, что не является lvalue . Следовательно, массив не может использоваться в левой части выражения присваивания.


Для следующего:

char *s = "foobar";      //works

Строковый литерал "foobar" хранится как анонимный массив из char и как инициализатор он распадается на указатель на свой первый элемент. Таким образом, приведенное выше эквивалентно:

char *s = &(("foobar")[0]);      //works

Инициализатор имеет тот же тип, что и s (char *), поэтому все в порядке.

Для последующего присвоения:

s[1] = 'z';              //doesn't work

Это синтаксически правильно, но нарушает ограничение, приводя к неопределенному поведению . Ограничение, которое нарушается, состоит в том, что анонимные массивы, созданные строковыми литералами, не могут быть изменены . Присвоение элементу такого массива является модификацией и не допускается.

Последующее присвоение:

s = "foobar";            //unlike arrays, works here

эквивалентно:

s = &(("foobar")[0]);            //unlike arrays, works here

Он присваивает значение char * переменной типа char *, так что это нормально.


Сравните следующее использование инициализатора "foobar":

char *s = "foobar";      //works

с его использованием в предыдущем объявлении:

char s[9] = "foobar";  //ok

Существует специальное правило инициализации, которое позволяет инициализировать массив char строковым литералом, необязательно заключенным в фигурные скобки. Это правило инициализации используется для инициализации char s[9].

Строковый литерал, используемый для инициализации массива, также создает анонимный массив char (по крайней мере, теоретически), но нет способа получить доступ к этому анонимному массиву of char, поэтому он может быть исключен из вывода компилятора. Это контрастирует с анонимным массивом char, созданным строковым литералом, используемым для инициализации char *s, к которому можно получить доступ через s.

1 голос
/ 07 мая 2020

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

  1. char s[9] = "foobar"; -> Инициализировать char массив s строковым литералом "foobar". Правильно.

  2. s[1] = 'z' -> Назначьте символьную константу 'z' второму элементу. из char массива s. Правильно.

  3. char s[9]; s = "foobar"; -> Объявите массив char a, затем попытайтесь присвоить строковый литерал "foobar" char массив. Не допустимо. Фактически вы не можете назначать массивы в C, вы можете только инициализировать массив char строкой при определении самого массива. Вот в чем разница. Если вы хотите скопировать строку в массив char, используйте вместо этого strcpy(s, "foobar");.

  4. char *s = "foobar"; -> Определите указатель на char s и инициализируйте его чтобы указать на строковый литерал "foobar". Правильно.

  5. s[1] = 'z'; -> Попытка изменить строковый литерал "foobar", на который указывает s. Не допустимо. Строковый литерал хранится в постоянной памяти.

  6. char *s; s = "foobar"; -> Объявить указатель на char s. Затем назначьте указатель на строковый литерал "foobar". Правильно.

0 голосов
/ 07 мая 2020

Может быть полезно думать о C как о запрете вам делать что-либо с массивами, за исключением помощи в некоторых особых случаях. C возник, когда языки программирования лишь помогали вам перемещать отдельные байты и «слова» (2 или, может быть, 4 байта) и выполнять простые арифметические c и операции с ними. Имея это в виду, давайте посмотрим на ваши примеры:

char s[9] = "foobar"; //ok

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

s[1] = 'z' //also ok

Да, это просто перемещает значение одного символа в один элемент массива.

char s[9]; s = "foobar" //doesn't work. Why?

Это не работает, потому что здесь нет помощи . s и "foobar" оба являются массивами, но C не предусматривает обработки массива как одного целого объекта.

Однако, хотя C не обрабатывает массив как целый объект, он действительно помогает при работе с массивами. Поскольку компилятор не мог работать с целыми массивами, программистам требовались другие способы работы с массивами. Таким образом, C получил функцию, которая, когда вы использовали массив в выражении, компилятор автоматически преобразовывал бы его в указатель на первый элемент массива, и это помогло бы программисту писать код для работы с элементами массив. Мы видим это в следующем примере:

char *s = "foobar"; //works

char *s объявляет s как указатель на char. Затем строковый литерал "foobar" представляет массив. Выше мы видели, что использование строкового литерала для инициализации массива было особым случаем. Однако здесь строковый литерал не используется для инициализации массива. Он используется для инициализации указателя, поэтому особые правила не применяются. В этом случае массив, представленный строковым литералом, автоматически преобразуется в указатель на его первый элемент. Итак, s инициализируется как указатель на первый элемент массива, содержащего «f», «o», «o», «b», «a», «r» и нулевой символ.

s[1] = 'z'; //doesn't work

Массивы, определенные строковыми литералами, должны быть константами. Они доступны только для чтения в том смысле, что стандарт C не определяет, что происходит, когда вы пытаетесь их изменить. Во многих реализациях C они назначаются памяти, доступной только для чтения, потому что операционная система и компьютерное оборудование не позволяют записывать в нее обычными программными средствами. Таким образом, s[1] = 'z'; может получить исключение (ловушку), предупреждение или сообщение об ошибке от компилятора. (В идеале char *s = "foobar"; было бы запрещено, потому что "foobar", будучи константой, будет иметь тип const char [7]. Однако, поскольку const не существовало в раннем C, типы строковых литералов не имеют const.)

char *s; s = "foobar"; //unlike arrays, works here

Здесь s - это char *, а строковый литерал "foobar" автоматически преобразуется в указатель на свой первый элемент, и этот указатель имеет вид char *, поэтому присвоение в порядке.

...