Чому я не намагаюся це змінити?
Тому що це невизначена поведінка. Цитата проекту 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), і ми, звичайно, можемо його змінити.