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



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



char s[] = "hello";


или такой:



char *s = "hello";


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

835   12  

12 ответов:

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

char *s = "Hello world";

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

как делать:

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

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

char s[] = "hello";

создает один'. Где этот массив выделяется в памяти, и как долго он живет, зависит от того, где объявление. Если объявление находится внутри функции, оно будет жить до конца блока, в котором оно объявлено, и почти наверняка будет выделено в стеке; если оно находится вне функции, оно будет наверное храниться в "инициализированном сегменте данных", который загружается из исполняемого файла в записываемую память при запуске программы.

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

char *s ="hello";

создает два объекты:

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

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

С99 проект N1256

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

  1. инициализации char[]:

    char c[] = "abc";      
    

    это "больше магии", и описанного в 6.7.8/14 "инициализация":

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

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

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

    как и любой другой обычный массив, 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', '' },
t[] = { 'a', 'b', 'c' };

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

char *p = "abc";

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

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

программа:

#include <stdio.h>

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   x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

вывод: GCC хранит char* его в , не на .text.

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

 char s[] = "abc";

получаем:

17:   c7 45 f0 61 62 63 00    movl   x636261,-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 chars) и инициализирует массив путем копирования членов данного строкового литерала в массив.

char *s = "hello";

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

char s[] = "Hello world";

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

char *s = "hello";

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

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

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

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

char *str = "Hello";

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

char str[] = "Hello";

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

means str[0] = 'M';

изменит str на "Мелло".

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

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

по делу:

char *x = "fred";

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

char x[] = "fred";

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

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

Это может быть хорошая возможность указать, что "правильность const" - это "хорошая вещь". Всегда и везде, где вы можете, используйте ключевое слово "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, перехватываются во время компиляции. Нет во время выполнения космической скорости или штраф за пост.

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

--DBJ

Comments

    Ничего не найдено.