Тяга C99 N1256
Существует два различных варианта использования строковых литералов символов:
Инициализировать char[]
:
char c[] = "abc";
Это «больше волшебства», и описано в 6.7.8 / 14 «Инициализация»:
Массив символьного типа может быть инициализирован литералом символьной строки, необязательно заключенным в фигурные скобки. Последовательные символы литерала символьной строки (включая завершающий нулевой символ, если есть место или если массив имеет неизвестный размер) инициализируют элементы массива.
Так что это просто ярлык для:
char c[] = {'a', 'b', 'c', '\0'};
Как и любой другой обычный массив, c
может быть изменен.
Везде: генерирует:
Поэтому, когда вы пишете:
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
.
Однако обратите внимание, что скрипт компоновщика по умолчанию помещает .rodata
и .text
в тот же сегмент , который имеет исполняемый файл, но не имеет разрешения на запись. Это можно наблюдать с:
readelf -l a.out
который содержит:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
Если мы сделаем то же самое для char[]
:
char s[] = "abc";
мы получаем:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
поэтому он сохраняется в стеке (относительно %rbp
).