Perché ricevo un errore di segmentazione quando scrivo su una stringa inizializzata con & # 8220; char * s & # 8221; ma non & # 8220; char s [] & # 8221 ;?

StackOverflow https://stackoverflow.com/questions/164194

  •  03-07-2019
  •  | 
  •  

Domanda

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 funziona perfettamente:

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

Testato con MSVC e GCC.

È stato utile?

Soluzione

Consulta le FAQ C, Domanda 1.32

  

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

     

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

     
      
  1. Come inizializzatore per un array di caratteri, come nella dichiarazione di char a [] , specifica i valori iniziali   dei personaggi in quell'array (e,   se necessario, le sue dimensioni).
  2.   
  3. Ovunque altro, si trasforma in una matrice statica di nomi senza nome,   e questo array senza nome può essere memorizzato   nella memoria di sola lettura e quale   quindi non può essere necessariamente   modificato. In un contesto di espressione,   l'array viene convertito immediatamente in a   puntatore, come al solito (vedere la sezione 6), quindi   la seconda dichiarazione inizializza p   per indicare il primo array senza nome   elemento.
  4.   
     

Alcuni compilatori hanno un interruttore   controllando se letterali stringa   sono scrivibili o meno (per compilare vecchi   codice) e alcuni potrebbero avere opzioni per   causa formali letterali in modo formale   trattato come array di const char (per   migliore rilevazione degli errori).

Altri suggerimenti

Normalmente, i letterali di 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 * str punta al primo carattere. Il segfault si verifica quando si tenta di cambiare il primo carattere in 'z' .

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

printf("%p", str);

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

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

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 sono presenti 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.

  

Perché viene visualizzato un errore di segmentazione durante la scrittura su una stringa?

Bozza C99 N1256

Esistono due diversi usi dei letterali delle stringhe di caratteri:

  1. Inizializza char [] :

    char c[] = "abc";      
    

    Questo è "più magico", e descritto in 6.7.8 / 14 "Inizializzazione":

      

    Un array di tipo di carattere può essere inizializzato da una stringa di caratteri letterale, facoltativamente   racchiuso tra parentesi graffe. Caratteri successivi della stringa di caratteri letterali (incluso il   terminare il carattere null se c'è spazio o se l'array ha dimensioni sconosciute) inizializzare il file   elementi dell'array.

    Quindi questa è solo una scorciatoia per:

    char c[] = {'a', 'b', 'c', '\0'};
    

    Come qualsiasi altro array normale, c può essere modificato.

  2. Ovunque altro: 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 [] a char * , che è sempre legale.

    Quindi, se si modifica c [0] , si modifica anche __unname , che è UB.

    Questo è documentato in 6.4.5 "Letterali di stringa":

      

    5 Nella fase di traduzione 7, un byte o un codice di valore zero viene aggiunto a ciascun multibyte   sequenza di caratteri risultante da una stringa letterale o letterale. Il personaggio multibyte   la sequenza viene quindi utilizzata per inizializzare un array di durata e lunghezza della memoria statica   sufficiente per contenere la sequenza. Per i letterali di stringhe di caratteri, gli elementi dell'array hanno   digitare char e vengono inizializzati con i singoli byte del carattere multibyte   sequenza [...]

         

    6 Non è specificato se questi array siano distinti, purché i loro elementi abbiano   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 " plain " char array oggetti s e t i cui elementi sono inizializzati con valori letterali 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 " puntatore al carattere " e lo inizializza per puntare a un oggetto con tipo "array di caratteri" 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;
}

Compila e decompila:

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   
 char s[] = "abc";
x0,-0x8(%rbp) f: 00 c: R_X86_64_32S .rodata

Conclusione: GCC memorizza char * nella sezione .rodata , non in .text .

Se facciamo lo stesso per char [] :

17:   c7 45 f0 61 62 63 00    movl   
readelf -l a.out
x636261,-0x10(%rbp)

otteniamo:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

quindi viene archiviato nello stack (rispetto a % rbp ).

Si noti tuttavia che lo script del linker predefinito inserisce .rodata e .text nello stesso segmento, che ha eseguito ma senza autorizzazione di scrittura. Questo può essere osservato con:

<*>

che contiene:

<*>

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

Nel secondo codice, " string " è 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.

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

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

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

array di stringhe

 char strarray[] = "hello";

Nell'array di memoria è memorizzato in celle di memoria continue, memorizzato come [h] [e] [l] [l] [o] [\ 0] = > [] è 1 char byte dimensioni della cella di memoria, e queste celle di memoria continue possono essere accedute dal nome chiamato strarray qui. quindi qui array di stringhe strarray stesso contenente tutti i caratteri di stringa inizializzati ad esso. in questo caso qui " hello " ; così possiamo facilmente cambiare il suo contenuto di memoria accedendo a ciascun personaggio con il 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' , quindi il valore di strarray è 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 è valido

Pointer

Come sappiamo tutti i puntatori puntano alla posizione della memoria nella memoria, il puntatore non inizializzato punta a una posizione di memoria casuale quindi e dopo l'inizializzazione punta a una particolare posizione di memoria

char *ptr = "hello";

qui il puntatore ptr è inizializzato sulla stringa " hello " che è una stringa costante memorizzata nella memoria di sola lettura (ROM), quindi " hello " non può essere modificato come è memorizzato nella ROM

e ptr è memorizzato nella sezione stack e punta alla 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ò puntare a qualsiasi indirizzo di memoria di una variabile del suo tipo di dati

ptr="new string"; is valid
char *str = "string";  

Quanto sopra imposta str in modo che punti al valore letterale " string " che è hardcoded 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.

char *str = "string";

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

char str[] = "string";

alloca e inizializza un array locale che è modificabile

Le FAQ di 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 nella tua sorgente) viene usata ovunque diverso da per inizializzare un array di caratteri (ovvero: il secondo esempio di @ Mark, che funziona correttamente), quella stringa è memorizzata dal compilatore in una speciale tabella di stringhe statiche , simile alla creazione di una variabile statica globale ( di sola lettura, ovviamente) che è essenzialmente anonimo (non ha una variabile "nome"). La parte di sola lettura è la parte importante ed è il motivo per cui il primo esempio di codice segfaults di @ Mark.

Il

 char *str = "string";
La riga

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

  str[0] = 'z';

ricevi 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 copie la stringa letterale in quella matrice, che è completamente scrivibile, quindi l'aggiornamento successivo non è un problema.

Letterali di stringa come " string " 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.

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

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

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

Supponiamo che le stringhe siano,

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

Nel primo caso, il valore letterale deve essere copiato quando 'a' entra nel campo di applicazione. 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 (è specifica 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 sullo stack (ambito locale) e che fa riferimento a una stringa letterale (dati di programma o testo) memorizzati altrove. Di solito la modifica di tale memoria non è una buona pratica né incoraggiata.

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

L'errore di segmentazione

è causato quando si accede alla memoria che non è accessibile.

char * str è un puntatore a una stringa che non è modificabile (il motivo per ottenere un errore seg) ..

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top