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";
определяет "простые" объекты массива 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