Perché viene visualizzato un errore di segmentazione quando si scrive su una stringa?
Bozza C99 N1256
Esistono due diversi usi dei letterali stringa di caratteri:
Inizializza char[]
:
char c[] = "abc";
Questo è "più magico", e descritto al 6.7.8 / 14 "Inizializzazione":
Una matrice di tipo di carattere può essere inizializzata da una stringa di caratteri letterale, facoltativamente racchiusa tra parentesi graffe. I caratteri successivi della stringa di caratteri letterali (incluso il carattere null di terminazione se c'è spazio o se la matrice ha dimensioni sconosciute) inizializzano gli elementi della matrice.
Quindi questa è solo una scorciatoia per:
char c[] = {'a', 'b', 'c', '\0'};
Come qualsiasi altro array normale, c
può essere modificato.
Ovunque: genera un:
Quindi quando scrivi:
char *c = "abc";
Questo è simile a:
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
Nota il cast implicito da char[]
achar *
, che è sempre legale.
Quindi, se si modifica c[0]
, si modifica anche__unnamed
, che è UB.
Questo è documentato in 6.4.5 "String letterals":
5 Nella fase di traduzione 7, un byte o un codice di valore zero viene aggiunto a ciascuna sequenza di caratteri multibyte risultante da una stringa letterale o letterale. La sequenza di caratteri multibyte viene quindi utilizzata per inizializzare una matrice di durata e lunghezza della memoria statica appena sufficiente per contenere la sequenza. Per i letterali stringa di caratteri, gli elementi dell'array hanno tipo char e vengono inizializzati con i singoli byte della sequenza di caratteri multibyte [...]
6 Non è specificato se questi array siano distinti, purché i loro elementi abbiano i valori appropriati. Se il programma tenta di modificare un tale array, il comportamento non è definito.
6.7.8 / 32 "Inizializzazione" fornisce un esempio diretto:
ESEMPIO 8: La dichiarazione
char s[] = "abc", t[3] = "abc";
definisce oggetti "semplici" di array di caratteri s
et
cui elementi sono inizializzati con valori letterali di stringa di caratteri.
Questa dichiarazione è identica a
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };
Il contenuto degli array è modificabile. D'altra parte, la dichiarazione
char *p = "abc";
definisce p
con il tipo "pointer to char" e lo inizializza in modo che punti a un oggetto con tipo "array of char" con lunghezza 4 i cui elementi sono inizializzati con una stringa di caratteri letterale. Se si tenta di utilizzare p
per modificare il contenuto dell'array, il comportamento non è definito.
Implementazione ELF GCC 4.8 x86-64
Programma:
#include <stdio.h>
int main(void) {
char *s = "abc";
printf("%s\n", s);
return 0;
}
Compilare e decompilare:
gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o
L'output contiene:
char *s = "abc";
8: 48 c7 45 f8 00 00 00 movq $0x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
Conclusione: GCC lo memorizza char*
nella .rodata
sezione, non in .text
.
Se facciamo lo stesso per char[]
:
char s[] = "abc";
otteniamo:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
quindi viene memorizzato nello stack (rispetto a %rbp
).
Si noti tuttavia che lo script del linker predefinito inserisce .rodata
e si trova .text
nello stesso segmento, che ha l'esecuzione ma nessuna autorizzazione di scrittura. Questo può essere osservato con:
readelf -l a.out
che contiene:
Section to Segment mapping:
Segment Sections...
02 .text .rodata