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

429

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

char s[] = "hello";

или вот так:

char *s = "hello";

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

  • 10
    c-faq.com/aryptr/index.html c-faq.com/charstring/index.html
  • 7
    char * s = "hello", здесь s может указывать любую другую строку во время выполнения. Я имею в виду, что это не константный указатель, вы можете присвоить другое значение во время выполнения p = "Nishant", в то время как s [] здесь s - постоянный указатель .. ..it нельзя переназначить другую строку, но мы можем назначить другое символьное значение в s [index].
Теги:
string
char
constants

12 ответов

466
Лучший ответ

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

char *s = "Hello world";

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

Выполняя:

char s[] = "Hello world";

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

s[0] = 'J';

легальным.

  • 20
    Литеральная строка "Hello world" находится в «доступной только для чтения части памяти» в обоих примерах. Пример с массивом указывает на него, пример с массивом копирует символы в элементы массива.
  • 24
    pmg: во втором случае буквальная строка вовсе не обязательно существует в памяти как отдельный смежный объект - это всего лишь инициализатор, компилятор вполне резонно может генерировать серию инструкций «загрузить немедленный байт», которые содержат значения символов, встроенные в их.
Показать ещё 16 комментариев
130

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

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

62

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

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 в памяти зависит от того, где появляется объявление (как в первом примере).

  • 1
    В обеих декларациях для "hello" память выделяется во время компиляции?. И еще одна вещь, char * p = "hello", здесь "hello" хранится в текстовом сегменте, как вы указали в своем ответе ... и как насчет char s [] = "привет", он также будет сначала сохранять в части текстового сегмента, и во время выполнения он будет копироваться в стеке, как сказал Рикард в ответе. пожалуйста, уточните этот момент.
  • 2
    @Nishant: В случае char s[] = "hello" "hello" - это просто инициализатор, сообщающий компилятору, как должен инициализироваться массив. Это может приводить или не приводить к соответствующей строке в текстовом сегменте - например, если s имеет статическую длительность хранения, то вполне вероятно, что единственный экземпляр "hello" будет в инициализированном сегменте данных - сам объект s . Даже если s имеет автоматическую продолжительность хранения, он может быть инициализирован последовательностью литеральных хранилищ, а не копией (например, movl $1819043176, -6(%ebp); movw $111, -2(%ebp) ).
Показать ещё 4 комментария
51

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

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

  • 1
    Я думаю, что гипотетическая карта памяти облегчает понимание!
28

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() {
    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 
  • 3
    Спасибо за отличный ответ :)
15
char s[] = "hello";

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

char *s = "hello";

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

  • 1
    Какой метод предпочтительнее использовать в функциях, если s не изменится, f (const char s []) или f (const char * s)?
  • 1
    @psihodelia: в объявлении функции нет разницы. В обоих случаях s является указателем на const char .
4
char s[] = "Hello world";

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

char *s = "hello";

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

  • 0
    @bo Persson Почему блок символов не может быть изменен во втором случае?
3

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

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

и

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

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

*(x + 1) = 'a';

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

  • 0
    Это ничем не отличается от x[1] = 'a'; который также будет зависать (в зависимости от платформы, конечно).
3

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

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

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

2
char *str = "Hello";

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

char str[] = "Hello";

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

means str[0] = 'M';

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

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

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

0

В случае:

char *x = "fred";

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

char x[] = "fred";

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

  • 2
    Технически, x является неизменяемым lvalue. Тем не менее, почти во всех контекстах он будет указывать на свой первый элемент, и это значение является значением r.
0

В свете комментариев здесь должно быть очевидно, что: 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

  • 0
    Это все правильно, но это не имеет ничего общего с вопросом. А что касается вашего предположения о компиляторе C ++, вопрос помечен как C, а не как C ++.
  • 0
    В char * s = "const string" нет ничего плохого;

Ещё вопросы

Сообщество Overcoder
Наверх
Меню