Dlaczego nie powinienem próbować tego zmieniać?
Ponieważ jest to niezdefiniowane zachowanie. Cytat z C99 N1256, szkic 6.7.8 / 32 "Inicjalizacja" :
PRZYKŁAD 8: Deklaracja
char s[] = "abc", t[3] = "abc";
określa „zwykły” obiektów tablicy char s
i t
którego elementy są inicjalizowane napisowych charakter.
Ta deklaracja jest identyczna z
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
Zawartość tablic można modyfikować. Z drugiej strony deklaracja
char *p = "abc";
definiuje p
typem „wskaźnik do znaku” i inicjalizuje go, aby wskazywał na obiekt typu „tablica znaków” o długości 4, którego elementy są inicjalizowane literałem będącym ciągiem znaków. Jeśli zostanie podjęta próba p
zmodyfikowania zawartości tablicy, zachowanie jest niezdefiniowane.
Dokąd oni poszli?
GCC 4.8 x86-64 ELF Ubuntu 14.04:
char s[]
: stos
char *s
:
.rodata
sekcja pliku obiektowego
- ten sam segment, w którym
.text
jest zrzucana sekcja pliku obiektowego, która ma uprawnienia do odczytu i wykonywania, ale nie ma uprawnień do zapisu
Program:
#include <stdio.h>
int main() {
char *s = "abc";
printf("%s\n", s);
return 0;
}
Kompiluj i dekompiluj:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
Wyjście zawiera:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
Więc ciąg jest przechowywany w .rodata
sekcji.
Następnie:
readelf -l a.out
Zawiera (uproszczone):
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
Oznacza to, że domyślny skrypt konsolidujący zrzuca zarówno .text
i .rodata
do segmentu, który można wykonać, ale nie można go modyfikować ( Flags = R E
). Próba zmodyfikowania takiego segmentu prowadzi do segfaulta w Linuksie.
Jeśli zrobimy to samo dla char[]
:
char s[] = "abc";
otrzymujemy:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
więc zostaje zapisany na stosie (względem %rbp
) i oczywiście możemy go zmodyfikować.