В чем разница между char s [] и char * s? - PullRequest
479 голосов
/ 10 ноября 2009

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

char s[] = "hello";

или как это:

char *s = "hello";

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

Ответы [ 13 ]

525 голосов
/ 10 ноября 2009

Разница в том, что

char *s = "Hello world";

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

Делая:

char s[] = "Hello world";

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

s[0] = 'J';

юридическое.

143 голосов
/ 10 ноября 2009

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

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

68 голосов
/ 10 ноября 2009

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

char s[] = "hello";

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

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

char *s ="hello";

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

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

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

57 голосов
/ 10 ноября 2009

с учетом деклараций

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

30 голосов

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";

определяет "простые" объекты массива char 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 в один и тот же сегмент, который имеет разрешение на выполнение, но не имеет разрешения на запись. Это можно наблюдать с помощью:

readelf -l a.out

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

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata
15 голосов
/ 10 ноября 2009
char s[] = "hello";

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

char *s = "hello";

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

4 голосов
/ 10 ноября 2009
char s[] = "Hello world";

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

char *s = "hello";

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

3 голосов
/ 30 ноября 2015

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

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

И

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

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

*(x + 1) = 'a';

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

3 голосов
/ 21 ноября 2012

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

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

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

2 голосов
/ 11 ноября 2016
char *str = "Hello";

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

char str[] = "Hello";

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

means str[0] = 'M';

изменит строку на "Mello".

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

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

...