Почему я могу переназначить новую строку для указателя на символ в C, например, так (ch * string = "hello"; string = "assign";) - PullRequest
0 голосов
/ 24 октября 2019

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

char *string = "hello";
string = "changed";
string = "changed again";

Это не вызывает никаких проблем с компилятором.

Но у меня сложилось впечатление, что массивы char инициализируются какуказатель (char * string в отличие от char string []) доступен только для чтения и не может быть перезаписан?

Ответы [ 3 ]

5 голосов
/ 24 октября 2019

Строковые данные могут быть расположены в памяти следующим образом:

Addr Contents
---- --------
1000 h e l l o \0
1006 c h a n g e d \0
1014 c h a n g e d   a g a i n \0

Когда вы инициализируете переменную string, она содержит адрес 1000. Первое переназначение изменяет его на 1006, второе меняет его на 1014. Строковые данные сами по себе не перезаписываются.

5 голосов
/ 24 октября 2019

Подумайте об этом (объяснения последуют). Существуют большие различия между

char *string1 = "hello";
char *string2 = "world";
string1 = string2;

и

char string3[] = "hello";
char string4[] = "world";
strcpy(string3, string4);

и

char *string5 = "hello";
char *string6 = "world";
strcpy(string5, string6);            /* WRONG */

и

char string7[] = "hello";
char string8[] = "world";
string7 = string8;                   /* VERY WRONG */

Убедитесь, что вы понимаете, как1/2 и 3/4 корпуса разные, но оба работают. Убедитесь, что вы понимаете, почему оба случая 5/6 и 7/8 являются неправильными и не будут работать (но по разным причинам).


Пояснения:

После

char string3[] = "hello";
char string4[] = "world";

у вас есть два массива, инициализированных в памяти, которые выглядят так:

         +---+---+---+---+---+---+
string3: | h | e | l | l | o |\0 |
         +---+---+---+---+---+---+

         +---+---+---+---+---+---+
string4: | w | o | r | l | d |\0 |
         +---+---+---+---+---+---+

И после вызова

strcpy(string3, string4);

вы получите

         +---+---+---+---+---+---+
string3: | w | o | r | l | d |\0 |
         +---+---+---+---+---+---+

         +---+---+---+---+---+---+
string4: | w | o | r | l | d |\0 |
         +---+---+---+---+---+---+

strcpy скопированные символы от array4 до array3. (Обратите также внимание, что исходные string3 и string4 оказались одинаковой длины. Это также сработало бы, если бы string4 было короче, но если бы string4 было длиннее, был бы массивпереполнение при копировании в string3.)

Теперь к регистру указателей. После

char *string1 = "hello";
char *string2 = "world";

у вас есть два анонимных массива, обычно в постоянной памяти, на которые указывают две переменные-указатели, которые выглядят так:

         +-----------+      +---+---+---+---+---+---+
string1: |     *----------->| h | e | l | l | o |\0 |
         +-----------+      +---+---+---+---+---+---+

         +-----------+      +---+---+---+---+---+---+
string2: |     *----------->| w | o | r | l | d |\0 |
         +-----------+      +---+---+---+---+---+---+

И затем после

string1 = string2;

указатели переставлены так:

         +-----------+      +---+---+---+---+---+---+
string1: |     *     |      | h | e | l | l | o |\0 |
         +-----|-----+      +---+---+---+---+---+---+
               |
               +--------------+
                              |
                              V
         +-----------+      +---+---+---+---+---+---+
string2: |     *----------->| w | o | r | l | d |\0 |
         +-----------+      +---+---+---+---+---+---+

Но у меня сложилось впечатление, что массивы char инициализируются как указатель (char * string, а не char char []) доступны только для чтения и не могут быть перезаписаны?

Верно. И это то, что идет не так в случае

char *string5 = "hello";
char *string6 = "world";
strcpy(string5, string6);            /* WRONG */

. strcpy пытается записать новые символы в string5, что не удается, если указанная строка была сохранена в постоянной памяти. По той же причине вы не можете делать что-то вроде

string5[0] = 'H';                    /* WRONG */
0 голосов
/ 24 октября 2019

"Но у меня сложилось впечатление, что массивы char, инициализированные как указатель (строка char * в отличие от строки char []), доступны только для чтения и не могут быть перезаписаны?"

Это правда "своего рода сорта", в зависимости от того, как вы на это смотрите. Когда компилятор выполняет присвоение литералу, он выделяет немного памяти для литерала и указывает на него char*. В зависимости от компилятора и архитектуры вы не сможете изменить эту память. Во всех случаях вы должны никогда пытаться изменить эту память (ведь вы не знаете, где она была). Таким образом, фактическая память, в которой хранится строковый литерал (фактические значения символов), вполне может быть доступна только для чтения.

В случае, как вы показали, вы не меняете память, содержащую символы. Вы меняете адрес, на который указывает string. Предполагая, что вы определяете string в функции, значение будет выделено в стеке. Это будет sizeof(void *), и эта память может быть изменена… она изменяется при изменении того, на что указывает string. Вы меняете адрес, на котором хранится «измененный», на адрес, на котором хранится «измененный».

...