Чому я не намагаюся це змінити?
Тому що це невизначена поведінка. Цитата проекту C99 N1256 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
з типом "покажчик на char" та ініціалізує його для вказівки на об'єкт типу "масив char" довжиною 4, елементи якого ініціалізовані літеральним рядком символів. Якщо буде здійснена спроба p
змінити вміст масиву, поведінка не визначена.
Куди вони йдуть?
GCC 4.8 x86-64 ELF Ubuntu 14.04:
char s[]
: стек
char *s
:
.rodata
розділ файлу об’єктів
- той самий сегмент, де
.text
потрапляє до розділу об’єктного файлу, який має дозволи Read і Exec, але не Write
Програма:
#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
Отже рядок зберігається в .rodata
розділі.
Тоді:
readelf -l a.out
Містить (спрощено):
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000704 0x0000000000000704 R E 200000
Section to Segment mapping:
Segment Sections...
02 .text .rodata
Це означає, що скрипт посилання за замовчуванням скидає .text
і .rodata
в сегмент, який можна виконати, але не змінити ( Flags = R E
). Спроба змінити такий сегмент призводить до сегментації в Linux.
Якщо ми робимо те саме для char[]
:
char s[] = "abc";
ми отримуємо:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
тому він зберігається в стеку (щодо %rbp
), і ми, звичайно, можемо його змінити.