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

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

char s[] = "hello";

или вот так:

char *s = "hello";

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

Ответы

Ответ 1

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

char *s = "Hello world";

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

Выполняя:

char s[] = "Hello world";

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

s[0] = 'J';

легальным.

Ответ 2

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

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() или аналогичного. Разумеется, если массив выделен в глобальной области, проблем нет.

Ответ 3

Это объявление:

char s[] = "hello";

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

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

char *s ="hello";

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

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

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

Ответ 4

Учитывая объявления

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 в С++) со статической продолжительностью хранения, что означает, что память для него выделяется, когда программа запускается и остается выделенной до тех пор, пока программа завершается. Попытка изменить содержимое строкового литерала вызывает поведение undefined.

Линия

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, чтобы указать на другой массив.

Ответ 5

Тяга C99 N1256

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

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

    char c[] = "abc";      
    

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

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

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

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

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

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

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

    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 <stdio.h>

int main(void) {
    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 в .rodata и тот же сегмент, который имеет исполняемый файл, но не имеет разрешения на запись. Это можно наблюдать с:

readelf -l a.out

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

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

Ответ 6

char s[] = "hello";

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

char *s = "hello";

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

Ответ 7

char s[] = "Hello world";

Здесь s - это массив символов, который может быть перезаписан, если мы хотим.

char *s = "hello";

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

Ответ 8

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

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

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

Ответ 9

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

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

и

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

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

*(x + 1) = 'a';

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

Ответ 10

char *str = "Hello";

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

char str[] = "Hello";

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

means str[0] = 'M';

изменит str на "Mello".

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

Почему возникает ошибка сегментации при записи в строку, инициализированную с помощью "char * s" но не "char s []" ,

Ответ 11

В случае:

char *x = "fred";

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

char x[] = "fred";

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

Ответ 12

В свете комментариев здесь должно быть очевидно, что: 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, попадают во время компиляции. Для const не существует пространства времени выполнения или скорости.

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

- DBJ

Ответ 13

char s[] = "Hello world"; является массивом символов, которые могут быть изменены, в то время как char *s = "hello"; указатель только для чтения.