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

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

char s[] = "hello";

или как это:

char *s = "hello";

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

вопрос задан 9.11.2009
root
9033 репутация

12 ответов


  • 486 рейтинг

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

    char *s = "Hello world";
    

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

    при этом:

    char s[] = "Hello world";
    

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

    s[0] = 'J';
    

    легальный.

    ответ дан Rickard, с репутацией 5889, 9.11.2009
  • 135 рейтинг

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

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

    ответ дан bdonlan, с репутацией 170015, 9.11.2009
  • 61 рейтинг

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

    char s[] = "hello";
    

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

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

    char *s ="hello";
    

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

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

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

    ответ дан caf, с репутацией 186458, 9.11.2009
  • 52 рейтинг

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

    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 существует только для области, в которой она объявлена) и копирует в нее адрес 3230269583 строкового литерала (в нашем примере 0x00008000). Обратите внимание, что поскольку s0 указывает на строковый литерал, его не следует использовать в качестве аргумента для любой функции, которая пытается его изменить (например, г. strtok(), strcat(), strcpy() и др. ).

    Линия

    char s1[] = "hello world";
    

    определяет s1 как массив из 12 элементов char (длина берется из строкового литерала) с автоматическим хранением и копирует содержимое 3230269583 литерала в массив в массив. Как видно из карты памяти, у нас есть две копии строки "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 для указания на другой массив.

    ответ дан John Bode, с репутацией 79189, 9.11.2009
  • 26 рейтинг

    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 
    
    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 
    
    ответ дан Ciro Santilli 新疆改造中心 六四事件 法轮功, с репутацией 124156, 5.06.2015
  • 14 рейтинг
    char s[] = "hello";
    

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

    char *s = "hello";
    

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

    ответ дан CB Bailey, с репутацией 493402, 9.11.2009
  • 4 рейтинг
    char s[] = "Hello world";
    

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

    char *s = "hello";
    

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

    ответ дан Sailaja, с репутацией 104, 9.11.2009
  • 3 рейтинг

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

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

    А:

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

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

    *(x + 1) = 'a';
    

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

    ответ дан Nick Louloudakis, с репутацией 3216, 30.11.2015
  • 3 рейтинг

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

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

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

    ответ дан Muzab, с репутацией 70, 21.11.2012
  • 2 рейтинг
    char *str = "Hello";
    

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

    char str[] = "Hello";
    

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

    means str[0] = 'M';
    

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

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

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

    ответ дан Mohit, с репутацией 527, 11.11.2016
  • 0 рейтинг

    В случае:

    char *x = "fred";
    

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

    char x[] = "fred";
    

    x это не lvalue, это rvalue - вы не можете присвоить ему.

    ответ дан Lee-Man, с репутацией 360, 9.11.2009
  • 0 рейтинг

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

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

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

    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 нет места во время выполнения или скорости.

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

    - DBJ

    ответ дан does_not_exist, с репутацией , 9.11.2009