В чем разница между char s и char * s?

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

char s[] = "hello"; 

или вот так:

 char *s = "hello"; 

Так в чем же разница? Я хочу знать, что на самом деле происходит с точки зрения продолжительности хранения, как при компиляции, так и во время выполнения.

Разница здесь в том, что

 char *s = "Hello world"; 

поместит "Hello world" в части только для чтения в памяти , и сделав указателем на это, любая операция записи в этой памяти будет незаконной.

Выполняя:

 char s[] = "Hello world"; 

помещает литеральную строку в постоянную память и копирует строку в новую выделенную память в стеке. Таким образом,

 s[0] = 'J'; 

законны.

Во-первых, в аргументах функций они в точности эквивалентны:

 void foo(char *x); void foo(char x[]); // exactly the same in all respects 

В других контекстах char * выделяет указатель, а char [] выделяет массив. Где строка идет в первом случае, спросите вы? Компилятор тайно выделяет статический анонимный массив для хранения строкового литерала. Так:

 char *x = "Foo"; // is approximately equivalent to: static const char __secret_anonymous_array[] = "Foo"; char *x = (char *) __secret_anonymous_array; 

Обратите внимание, что вы никогда не должны пытаться модифицировать содержимое этого анонимного массива с помощью этого указателя; эффекты не определены (часто это означает сбой):

 x[1] = 'O'; // BAD. DON'T DO THIS. 

Использование синтаксиса массива напрямую выделяет его в новую память. Таким образом, модификация безопасна:

 char x[] = "Foo"; x[1] = 'O'; // No problem. 

Однако массив работает только до тех пор, пока его область охвата, поэтому, если вы сделаете это в функции, не возвращайте и не strdup() указатель на этот массив – вместо этого сделайте копию с помощью strdup() или аналогичного. Разумеется, если массив выделен в глобальной области, проблем нет.

Это заявление:

 char s[] = "hello"; 

Создает один объект – массив char размером 6, называемый s , инициализированный значениями 'h', 'e', 'l', 'l', 'o', '\0' . Если этот массив выделен в памяти и как долго он живет, зависит от того, где появляется объявление. Если декларация находится в пределах функции, она будет жить до конца блока, в котором она объявлена, и почти наверняка будет выделена в стеке; если он находится вне функции, он, вероятно, будет храниться в «инициализированном сегменте данных», который загружается из исполняемого файла в записываемую память при запуске программы.

С другой стороны, это заявление:

 char *s ="hello"; 

Создает два объекта:

  • массив, ansible только для чтения, из 6 char s, содержащий значения 'h', 'e', 'l', 'l', 'o', '\0' живет на всю жизнь программы); а также
  • переменная типа pointer-to-char, называемая s , которая инициализируется с расположением первого символа в этом неназванном, доступном только для чтения массиве.

Неименованный ansible только для чтения массив обычно находится в сегменте «текст» программы, что означает, что он загружается с диска в постоянную память вместе с самим кодом. Расположение переменной указателя s в памяти зависит от того, где появляется объявление (как в первом примере).

Учитывая декларации

 char *s0 = "hello world"; char s1[] = "hello world"; 

предположим следующую гипотетическую карту памяти:

                     0x01 0x02 0x03 0x04
         0x00008000: 'h' 'e' 'l' 'l'
         0x00008004: 'o' '' 'w' 'o'
         0x00008008: 'r' 'l' 'd' 0x00
         ...
 s0: 0x00010000: 0x00 0x00 0x80 0x00
 s1: 0x00010004: 'h' 'e' 'l' 'l'
         0x00010008: 'o' '' 'w' 'o'
         0x0001000C: 'r' 'l' 'd' 0x00

Строковый литерал "hello world" представляет собой 12-элементный массив char ( const char на C ++) со статической продолжительностью хранения, что означает, что память для него выделяется при запуске программы и остается выделенной до тех пор, пока программа не завершится. Попытка изменить содержимое строкового литерала вызывает неопределенное поведение.

Линия

 char *s0 = "hello world"; 

определяет s0 как указатель на char с длительностью автоматического хранения (что означает, что переменная s0 существует только для области, в которой она объявлена) и копирует адрес строкового литерала ( 0x00008000 в этом примере). Заметим, что поскольку s0 указывает на строковый литерал, он не должен использоваться в качестве аргумента для любой функции, которая попытается ее модифицировать (например, strtok() , strcat() , strcpy() и т. Д.).

Линия

 char s1[] = "hello world"; 

определяет s1 как 12-элементный массив char (длина берется из строкового литерала) с длительностью автоматического хранения и копирует содержимое литерала в массив. Как видно из карты памяти, у нас есть две копии строки "hello world" ; разница в том, что вы можете изменить строку, содержащуюся в s1 .

s0 и s1 взаимозаменяемы в большинстве контекстов; вот исключения:

 sizeof s0 == sizeof (char*) sizeof s1 == 12 type of &s0 == char ** type of &s1 == char (*)[12] // pointer to a 12-element array of char 

Вы можете переназначить переменную s0 чтобы указать на другой строковый литерал или на другую переменную. Вы не можете переназначить переменную s1 чтобы указать на другой массив.

C99 N1256 черновик

Существует два совершенно разных применения литералов массива:

  1. Инициализировать char[] :

     char c[] = "abc"; 

    Это «более волшебное» и описано в 6.7.8 / 14 «Инициализация» :

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

    Так что это просто ярлык для:

     char c[] = {'a', 'b', 'c', '\0'}; 

    Как и любой другой регулярный массив, c может быть изменен.

  2. Всюду: генерирует:

    • неназванный
    • array of char Каков тип строковых литералов в C и C ++?
    • со статическим хранилищем
    • который дает UB, если он изменен

    Поэтому, когда вы пишете:

     char *c = "abc"; 

    Это похоже на:

     /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed; 

    Обратите внимание на неявный перевод из char[] в char * , который всегда легален.

    Затем, если вы измените c[0] , вы также измените __unnamed , что является UB.

    Это описано в разделе 6.4.5 «Строковые литералы» :

    5 В фазе 7 перевода байт или код нулевого значения добавляется к каждой многобайтовой последовательности символов, которая получается из строкового литерала или литералов. Последовательность многобайтовых символов затем используется для инициализации массива статической продолжительности хранения и длины, достаточной для того, чтобы содержать последовательность. Для символьных строковых литералов элементы массива имеют тип char и инициализируются отдельными байтами многобайтовой последовательности символов […]

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

6.7.8 / 32 «Инициализация» дает прямой пример:

ПРИМЕР 8: Декларация

 char s[] = "abc", t[3] = "abc"; 

определяет «простые» объекты массива символов s и t , элементы которых инициализируются символами строковых символов.

Эта декларация идентична

 char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' }; 

Содержимое массивов может быть изменено. С другой стороны, декларация

 char *p = "abc"; 

определяет p с типом «указатель на символ» и инициализирует его, указывая на объект с типом «массив символов» с длиной 4, элементы которого инициализируются символьным строковым литералом. Если для изменения содержимого массива сделана попытка использовать p , поведение не определено.

GCC 4.8 x86-64 Реализация ELF

Программа:

 #include  int main() { char *s = "abc"; printf("%s\n", s); return 0; } 

Компилировать и декомпилировать:

 gcc -ggdb -std=c99 -c main.c objdump -Sr main.o 

Выход содержит:

  char *s = "abc"; 8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata 

Вывод: GCC хранит char* в разделе .rodata , а не в .text .

Если мы сделаем то же самое для char[] :

  char s[] = "abc"; 

мы получаем:

 17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp) 

поэтому он хранится в стеке (относительно %rbp ).

Обратите внимание, однако, что сценарий компоновщика по умолчанию ставит .rodata и .text в том же сегменте, который выполняет, но не имеет права на запись. Это можно наблюдать с помощью:

 readelf -l a.out 

который содержит:

  Section to Segment mapping: Segment Sections... 02 .text .rodata 
 char s[] = "hello"; 

объявляет s массивом char который достаточно длинный, чтобы удерживать инициализатор (5 + 1 char s) и инициализирует массив, копируя члены данного строкового литерала в массив.

 char *s = "hello"; 

объявляет s указателем на один или несколько (в этом случае больше) char s и указывает его непосредственно на фиксированное (только для чтения) местоположение, содержащее буквальный "hello" .

 char s[] = "Hello world"; 

Здесь s – массив символов, который мы можем переписать, если хотим.

 char *s = "hello"; 

Строковый литерал используется для создания этих символьных блоков где-то в памяти, на которую указывает этот указатель s . Мы можем здесь переназначить объект, на который он указывает, изменив это, но пока он указывает на строковый литерал, блок символов, на который он указывает, не может быть изменен.

В качестве дополнения учтите, что для целей только для чтения использование обоих одинаково, вы можете получить доступ к символу путем индексирования с помощью формата [] или *( + ) :

 printf("%c", x[1]); //Prints r 

А также:

 printf("%c", *(x + 1)); //Prints r 

Очевидно, что если вы попытаетесь сделать

 *(x + 1) = 'a'; 

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

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

 printf("sizeof s[] = %zu\n", sizeof(s)); //6 printf("sizeof *s = %zu\n", sizeof(s)); //4 or 8 

Как упоминалось выше, для массива '\0' будет выделен конечный элемент.

 char *str = "Hello"; 

Вышеупомянутое устанавливает str для указания на буквальное значение «Hello», которое жестко закодировано в двоичном изображении программы, которое помечено как доступное только для чтения в памяти, означает, что любое изменение в этом строковом литерале является незаконным, и это приведет к возникновению ошибок сегментации.

 char str[] = "Hello"; 

копирует строку в новую выделенную память в стеке. Таким образом, любые изменения в нем разрешены и законны.

 means str[0] = 'M'; 

изменит str на «Mello».

Для получения более подробной информации, пожалуйста, перейдите по аналогичному вопросу:

Почему возникает segmentation fault при записи в строку, инициализированную символом «char * s», но не «char s []»?

В случае:

 char *x = "fred"; 

x – это lvalue – ему можно назначить. Но в случае:

 char x[] = "fred"; 

x не является lvalue, это rvalue – вы не можете назначить ему.

В свете комментариев здесь должно быть очевидно, что: char * s = “hello”; Это плохая идея, и ее следует использовать в очень узком пространстве.

Это может быть хорошей возможностью указать на то, что «const correctness» – это «хорошая вещь». Когда бы и где бы вы ни находились, используйте ключевое слово «const» для защиты своего кода, от «расслабленных» абонентов или программистов, которые обычно наиболее «расслаблены», когда указатели вступают в игру.

Достаточно мелодрамы, вот что можно достичь, когда украшающие указатели с «const». (Примечание: каждый должен читать декларации указателей справа налево.) Вот три разных способа защитить себя при игре с указателями:

 const DBJ* p means "p points to a DBJ that is const" 

– то есть объект DBJ не может быть изменен с помощью p.

 DBJ* const p means "p is a const pointer to a DBJ" 

– то есть вы можете изменить объект DBJ через p, но вы не можете изменить указатель p.

 const DBJ* const p means "p is a const pointer to a const DBJ" 

– то есть вы не можете изменить указатель p самостоятельно, и вы не можете изменить объект DBJ через p.

Ошибки, связанные с попытками мутаций const-ant, попадают во время компиляции. Для состязания нет времени выполнения или скорости.

(Предположительно, вы используете компилятор C ++, конечно?)

–DBJ

  • в чем причина явного объявления L или UL для длинных значений
  • Как установить переменные в сценариях HIVE
  • Как указать путь во время выполнения?
  • Почему я могу изменить локальную константную переменную с помощью указателей, но не глобальную в C?
  • Константы в Objective-C
  • Пул строк и постоянный пул
  • Почему мне разрешено использовать переменную const, определенную как размер массива в C?
  • C / C ++: оптимизация указателей на строковые константы
  • Почему в Java нет постоянной функции?
  • Статический readonly vs const
  • c #: Как использовать перечисление для хранения строковых констант?
  • Давайте будем гением компьютера.