Perché viene visualizzato un errore di segmentazione quando si scrive su un "char * s" inizializzato con una stringa letterale, ma non "char s []"?


288

Il seguente codice riceve un errore seg sulla linea 2:

char *str = "string";
str[0] = 'z';  // could be also written as *str = 'z'
printf("%s\n", str);

Mentre questo funziona perfettamente bene:

char str[] = "string";
str[0] = 'z';
printf("%s\n", str);

Testato con MSVC e GCC.


1
È divertente - ma questo in realtà si compila e funziona perfettamente quando si utilizza il compilatore di Windows (cl) su un prompt dei comandi per sviluppatori di Visual Studio. Mi ha confuso per alcuni momenti ...
David Refaeli,

Risposte:


242

Vedi le domande frequenti C, domanda 1.32

D : Qual è la differenza tra queste inizializzazioni?
char a[] = "string literal";
char *p = "string literal";
Il mio programma si arresta in modo anomalo se provo ad assegnare un nuovo valore a p[i].

A : Un letterale stringa (il termine formale per una stringa tra virgolette doppie in sorgente C) può essere usato in due modi leggermente diversi:

  1. Come inizializzatore per una matrice di caratteri, come nella dichiarazione di char a[], specifica i valori iniziali dei caratteri in quella matrice (e, se necessario, le sue dimensioni).
  2. In qualsiasi altro luogo, si trasforma in una matrice di caratteri statica senza nome e questa matrice senza nome può essere memorizzata in memoria di sola lettura e che pertanto non può essere necessariamente modificata. In un contesto di espressione, l'array viene convertito immediatamente in un puntatore, come al solito (vedere la sezione 6), quindi la seconda dichiarazione inizializza p in modo che punti al primo elemento dell'array senza nome.

Alcuni compilatori hanno un interruttore che controlla se i letterali di stringa sono scrivibili o meno (per compilare il vecchio codice), e alcuni possono avere opzioni per far sì che i letterali di stringa vengano trattati formalmente come array di const char (per una migliore rilevazione degli errori).


7
Coppia di altri punti: (1) il segfault avviene come descritto, ma il suo verificarsi è una funzione dell'ambiente di esecuzione; se lo stesso codice si trovava in un sistema incorporato, la scrittura potrebbe non avere alcun effetto o potrebbe effettivamente cambiare la s in una z. (2) Poiché i valori letterali di stringa non sono scrivibili, il compilatore può risparmiare spazio inserendo due istanze di "stringa" nello stesso posto; oppure, se da qualche altra parte nel codice hai "un'altra stringa", allora un pezzo di memoria potrebbe supportare entrambi i valori letterali. Chiaramente, se al codice fosse permesso di modificare quei byte, potrebbero verificarsi bug strani e difficili.
Greggo,

1
@greggo: buon punto. C'è anche un modo per farlo sui sistemi con MMU usando la mprotectprotezione di sola lettura (vedi qui ).

Quindi char * p = "blah" in realtà crea un array temporaneo? Strano.
Rahul Tyagi,

1
E dopo 2 anni di scrittura in C ++ ... TIL
zeboidlund,

@rahultyagi cosa intendi?
Suraj Jain,

105

Normalmente, i letterali stringa vengono archiviati nella memoria di sola lettura quando viene eseguito il programma. Questo per evitare di modificare accidentalmente una costante di stringa. Nel tuo primo esempio, "string"è memorizzato nella memoria di sola lettura e *strpunta al primo carattere. Il segfault si verifica quando si tenta di cambiare il primo personaggio in 'z'.

Nel secondo esempio, la stringa "string"viene copiata dal compilatore dalla sua home di sola lettura str[]all'array. Quindi è permesso cambiare il primo carattere. Puoi verificarlo stampando l'indirizzo di ciascuno:

printf("%p", str);

Inoltre, la stampa della dimensione di strnel secondo esempio ti mostrerà che il compilatore ha allocato 7 byte per esso:

printf("%d", sizeof(str));

13
Ogni volta che si utilizza "% p" su printf, è necessario eseguire il cast del puntatore su void * come in printf ("% p", (void *) str); Quando si stampa size_t con printf, è necessario utilizzare "% zu" se si utilizza l'ultimo standard C (C99).
Chris Young,

4
Inoltre, le parentesi con sizeof sono necessarie solo quando si assume la dimensione di un tipo (l'argomento sembra quindi un cast). Ricorda che sizeof è un operatore, non una funzione.
Rilassati il


34

La maggior parte di queste risposte sono corrette, ma solo per aggiungere un po 'più di chiarezza ...

La "memoria di sola lettura" a cui le persone si riferiscono è il segmento di testo in termini ASM. È lo stesso posto in memoria in cui sono caricate le istruzioni. Questo è di sola lettura per ovvi motivi come la sicurezza. Quando si crea un carattere * inizializzato su una stringa, i dati della stringa vengono compilati nel segmento di testo e il programma inizializza il puntatore per puntare al segmento di testo. Quindi se provi a cambiarlo, kaboom. Segfault.

Se scritto come un array, il compilatore inserisce invece i dati di stringa inizializzati nel segmento di dati, che è lo stesso posto in cui si trovano le variabili globali e tali. Questa memoria è mutabile, poiché non ci sono istruzioni nel segmento di dati. Questa volta, quando il compilatore inizializza l'array di caratteri (che è ancora solo un carattere *) punta verso il segmento di dati anziché il segmento di testo, che è possibile modificare in modo sicuro in fase di esecuzione.


Ma non è vero che ci possono essere implementazioni che consentono di modificare la "memoria di sola lettura"?
Pacerier,

Se scritto come un array, il compilatore inserisce i dati di stringa inizializzati nel segmento di dati se sono statici o globali. Altrimenti (ad es. Per un normale array automatico) si posiziona sullo stack, nel frame dello stack della funzione principale. Corretta?
SE

27

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:

  1. 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.

  2. 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 set 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 pcon 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 pper 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 .rodatasezione, 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 .rodatae si trova .textnello 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

17

Nel primo codice, "stringa" è una costante di stringa e le costanti di stringa non devono mai essere modificate perché spesso vengono inserite nella memoria di sola lettura. "str" ​​è un puntatore utilizzato per modificare la costante.

Nel secondo codice, "stringa" è un inizializzatore di array, una specie di scorciatoia per

char str[7] =  { 's', 't', 'r', 'i', 'n', 'g', '\0' };

"str" ​​è un array allocato nello stack e può essere modificato liberamente.


1
Nello stack o nel segmento di dati se strè globale o static.
Gauthier,

12

Perché il tipo di "whatever"nel contesto del primo esempio è const char *(anche se lo si assegna a un carattere non const *), il che significa che non si dovrebbe provare a scriverlo.

Il compilatore lo ha imposto mettendo la stringa in una parte di sola lettura della memoria, quindi scrivere su di essa genera un segfault.


8

Per capire questo errore o problema, dovresti prima conoscere la differenza tra il puntatore e l'array, quindi qui prima ho spiegato le differenze tra loro

array di stringhe

 char strarray[] = "hello";

In memoria di matrice è memorizzato in celle di memoria continue, memorizzato come [h][e][l][l][o][\0] =>[]è 1 carattere cella di memoria dimensione in byte, e questa celle di memoria continue può essere accessibili a nome chiamato strArray here.so qui stringa del campo strarraystesso contenente tutti i caratteri di stringa inizializzata a questo it.In qui in "hello" modo che possiamo facilmente cambiare il suo contenuto di memoria accedendo a ciascun carattere dal suo valore di indice

`strarray[0]='m'` it access character at index 0 which is 'h'in strarray

e il suo valore è cambiato in 'm'modo che il valore così strarray sia cambiato in "mello";

un punto da notare qui che possiamo cambiare il contenuto dell'array di stringhe cambiando carattere per carattere ma non possiamo inizializzare un'altra stringa direttamente su di essa come strarray="new string"non è valida

pointer

Come tutti sappiamo, il puntatore punta alla posizione della memoria nella memoria, il puntatore non inizializzato punta alla posizione della memoria casuale così e dopo l'inizializzazione punta a una particolare posizione della memoria

char *ptr = "hello";

qui il puntatore ptr è inizializzato su stringa "hello"che è una stringa costante memorizzata nella memoria di sola lettura (ROM), quindi "hello"non può essere modificata poiché è memorizzata nella ROM

e ptr è memorizzato nella sezione stack e punta a stringa costante "hello"

quindi ptr [0] = 'm' non è valido poiché non è possibile accedere alla memoria di sola lettura

Ma ptr può essere inizializzato direttamente su un altro valore di stringa poiché è solo un puntatore, quindi può essere puntato a qualsiasi indirizzo di memoria della variabile del suo tipo di dati

ptr="new string"; is valid

7
char *str = "string";  

Quanto sopra strpunta a indicare il valore letterale"string" codificato nell'immagine binaria del programma, che è probabilmente contrassegnato come di sola lettura in memoria.

Quindi str[0]=sta tentando di scrivere nel codice di sola lettura dell'applicazione. Immagino che questo probabilmente dipende dal compilatore.


6
char *str = "string";

alloca un puntatore a una stringa letterale, che il compilatore inserisce in una parte non modificabile dell'eseguibile;

char str[] = "string";

alloca e inizializza un array locale che è modificabile


possiamo scrivere int *b = {1,2,3) come scriviamo char *s = "HelloWorld"?
Suraj Jain,

6

Le FAQ C a cui @matli ha collegato la menziona, ma nessun altro qui lo ha ancora fatto, quindi per chiarimenti: se una stringa letterale (stringa tra virgolette doppie nel tuo sorgente) viene utilizzata in qualsiasi luogo diverso da inizializzare una matrice di caratteri (ad esempio: @ Secondo esempio di Mark, che funziona correttamente), quella stringa è memorizzata dal compilatore in una tabella di stringhe statica speciale , che è simile alla creazione di una variabile statica globale (di sola lettura, ovviamente) che è essenzialmente anonima (non ha un nome "variabile" "). La parte di sola lettura è la parte importante ed è il motivo per cui il primo esempio di codice del seg @ default segfaults.


possiamo scrivere int *b = {1,2,3) come scriviamo char *s = "HelloWorld"?
Suraj Jain,

4

Il

 char *str = "string";

line definisce un puntatore e lo punta a una stringa letterale. La stringa letterale non è scrivibile, quindi quando lo fai:

  str[0] = 'z';

hai un errore di seg. Su alcune piattaforme, il valore letterale potrebbe essere nella memoria scrivibile, quindi non vedrai un segfault, ma è un codice non valido (con conseguente comportamento indefinito) a prescindere.

La linea:

char str[] = "string";

alloca una matrice di caratteri e copia la stringa letterale in quella matrice, che è completamente scrivibile, quindi l'aggiornamento successivo non è un problema.


possiamo scrivere int *b = {1,2,3) come scriviamo char *s = "HelloWorld"?
Suraj Jain,

3

I letterali di stringa come "stringa" sono probabilmente allocati nello spazio degli indirizzi del tuo eseguibile come dati di sola lettura (dai o prendi il tuo compilatore). Quando vai a toccarlo, si spaventa che tu sia nella sua zona del costume da bagno e ti fa sapere con un errore di seg.

Nel tuo primo esempio, stai ottenendo un puntatore a quei dati const. Nel tuo secondo esempio, stai inizializzando un array di 7 caratteri con una copia dei dati const.


2
// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";

// create an array of characters like this 
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];

// now we try to change a character in the array first, this will work
*arr_p = 'E';

// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.


/*-----------------------------------------------------------------------------
 *  String constants can't be modified. A segmentation fault is the result,
 *  because most operating systems will not allow a write
 *  operation on read only memory.
 *-----------------------------------------------------------------------------*/

//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array. 

1

In primo luogo, strè un puntatore che punta a "string". Al compilatore è consentito inserire valori letterali stringa in posizioni in cui non è possibile scrivere, ma possono solo leggere. (Questo avrebbe dovuto innescare un avviso, dato che stai assegnando const char *a a char *. Avevi gli avvisi disabilitati o li hai semplicemente ignorati?)

In secondo luogo, stai creando un array, che è memoria a cui hai pieno accesso e inizializzalo "string". Stai creando un char[7](sei per le lettere, uno per la terminazione '\ 0') e fai quello che ti piace.


@Ferruccio,? Sì, il constprefisso crea variabili Sola lettura
EsmaeelE

Nella stringa C i letterali hanno tipo char [N], no const char [N], quindi non c'è nessun avviso. (Puoi cambiarlo in gcc almeno passando -Wwrite-strings.)
melpomene il

0

Supponiamo che le stringhe siano,

char a[] = "string literal copied to stack";
char *p  = "string literal referenced by p";

Nel primo caso, il letterale deve essere copiato quando 'a' entra in campo. Qui "a" è un array definito nello stack. Significa che la stringa verrà creata nello stack e i suoi dati verranno copiati dalla memoria di codice (testo), che è in genere di sola lettura (questo è specifico dell'implementazione, un compilatore può inserire questi dati di programma di sola lettura nella memoria di sola lettura) ).

Nel secondo caso, p è un puntatore definito nello stack (ambito locale) e che fa riferimento a una stringa letterale (dati di programma o testo) memorizzati altrove. Di solito modificare tale memoria non è una buona pratica né incoraggiato.


-1

La prima è una stringa costante che non può essere modificata. Il secondo è un array con valore inizializzato, quindi può essere modificato.


-2

L'errore di segmentazione è causato quando si tenta di accedere alla memoria inaccessibile.

char *str è un puntatore a una stringa non modificabile (la ragione per ottenere segfault).

mentre char str[]è un array e può essere modificabile ..

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.