Qual è la differenza tra char s e char * s?

In C, si può usare una stringa letterale in una dichiarazione come questa:

char s[] = "hello"; 

o in questo modo:

 char *s = "hello"; 

Quindi qual è la differenza? Voglio sapere cosa succede realmente in termini di durata dello storage, sia in fase di compilazione che in fase di esecuzione.

La differenza qui è quella

 char *s = "Hello world"; 

posizionerà "Hello world" nelle parti di sola lettura della memoria , e facendo un puntatore a che rende qualsiasi operazione di scrittura su questa memoria illegale.

Mentre fai:

 char s[] = "Hello world"; 

inserisce la stringa letterale nella memoria di sola lettura e copia la stringa nella memoria appena allocata nello stack. Così facendo

 s[0] = 'J'; 

legale.

Prima di tutto, negli argomenti di funzione, sono esattamente equivalenti:

 void foo(char *x); void foo(char x[]); // exactly the same in all respects 

In altri contesti, char * alloca un puntatore, mentre char [] alloca una matrice. Dove finisce la stringa nel primo caso, chiedi? Il compilatore alloca segretamente una matrice anonima statica per contenere la stringa letterale. Così:

 char *x = "Foo"; // is approximately equivalent to: static const char __secret_anonymous_array[] = "Foo"; char *x = (char *) __secret_anonymous_array; 

Nota che non devi mai tentare di modificare il contenuto di questo array anonimo tramite questo puntatore; gli effetti non sono definiti (spesso si intende un arresto):

 x[1] = 'O'; // BAD. DON'T DO THIS. 

L’uso della syntax dell’array lo assegna direttamente alla nuova memoria. Quindi la modifica è sicura:

 char x[] = "Foo"; x[1] = 'O'; // No problem. 

Tuttavia, la matrice dura solo fino al suo ambito di applicazione, quindi se lo fai in una funzione, non restituire o perdere un puntatore a questo array – crea una copia invece con strdup() o simile. Se l’array è allocato in ambito globale, ovviamente, nessun problema.

Questa dichiarazione:

 char s[] = "hello"; 

Crea un object – un array di char di dimensione 6, chiamato s , inizializzato con i valori 'h', 'e', 'l', 'l', 'o', '\0' . Dove questo array è allocato in memoria e per quanto tempo dura, dipende da dove appare la dichiarazione. Se la dichiarazione è all’interno di una funzione, vivrà fino alla fine del blocco in cui è dichiarata, e quasi certamente sarà assegnata allo stack; se è esterno a una funzione, verrà probabilmente memorizzato all’interno di un “segmento di dati inizializzato” che viene caricato dal file eseguibile nella memoria scrivibile quando viene eseguito il programma.

D’altra parte, questa dichiarazione:

 char *s ="hello"; 

Crea due oggetti:

  • un array di sola lettura di 6 char che contiene i valori 'h', 'e', 'l', 'l', 'o', '\0' , che non ha nome e ha durata di memorizzazione statica (nel senso che vive per l’intera vita del programma); e
  • una variabile di tipo pointer-to-char, chiamata s , che viene inizializzata con la posizione del primo carattere in quell’array senza nome, di sola lettura.

L’array di sola lettura senza nome si trova in genere nel segmento “testo” del programma, il che significa che viene caricato dal disco nella memoria di sola lettura, insieme al codice stesso. La posizione della variabile del puntatore s in memoria dipende da dove appare la dichiarazione (proprio come nel primo esempio).

Date le dichiarazioni

 char *s0 = "hello world"; char s1[] = "hello world"; 

assumere la seguente ipotetica mappa di memoria:

                     0x01 0x02 0x03 0x04
         0x00008000: 'h' 'e' 'l' 'l'
         0x00008004: 'o' '' 'w' 'o'
         0x00008008: 'r' 'l' 'd' 0x00
         ...
 s0: 0x00010000: 0x00 0x00 0x80 0x00
 s1: 0x00010004: 'h' 'e' 'l' 'l'
         0x00010008: 'o' '' 'w' 'o'
         0x0001000C: 'r' 'l' 'd' 0x00

La stringa letterale "hello world" è una matrice di char di 12 elementi ( const char in C ++) con durata di archiviazione statica, il che significa che la memoria viene allocata all’avvio del programma e rimane allocata fino alla fine del programma. Il tentativo di modificare il contenuto di una stringa letterale richiama il comportamento non definito.

La linea

 char *s0 = "hello world"; 

definisce s0 come puntatore al char con durata della memorizzazione automatica (ovvero la variabile s0 esiste solo per l’ambito in cui è dichiarata) e copia l’ indirizzo della stringa letterale ( 0x00008000 in questo esempio) su di esso. Si noti che poiché s0 punta a un letterale stringa, non dovrebbe essere usato come argomento per qualsiasi funzione che proverebbe a modificarlo (ad esempio, strtok() , strcat() , strcpy() , ecc.).

La linea

 char s1[] = "hello world"; 

definisce s1 come una matrice di char di 12 elementi (la lunghezza viene ricavata dalla stringa letterale) con la durata della memorizzazione automatica e copia il contenuto del letterale nella matrice. Come puoi vedere dalla mappa della memoria, abbiamo due copie della stringa "hello world" ; la differenza è che puoi modificare la stringa contenuta in s1 .

s0 e s1 sono intercambiabili nella maggior parte dei contesti; ecco le eccezioni:

 sizeof s0 == sizeof (char*) sizeof s1 == 12 type of &s0 == char ** type of &s1 == char (*)[12] // pointer to a 12-element array of char 

È ansible riassegnare la variabile s0 in modo che punti a un letterale stringa diverso oa un’altra variabile. Non è ansible riassegnare la variabile s1 in modo che punti a un altro array.

Tiraggio C99 N1256

Esistono due usi completamente diversi di letterali di array:

  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 un letterale stringa di caratteri, facoltativamente racchiuso tra parentesi graffe. I caratteri successivi della stringa di caratteri letterali (compreso il carattere null che termina se c’è spazio o se la matrice è di dimensioni sconosciute) inizializzano gli elementi dell’array.

    Quindi questa è solo una scorciatoia per:

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

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

  2. Ovunque: genera un:

    • Senza nome
    • array di char Qual è il tipo di stringa letterale in C e C ++?
    • con memoria statica
    • che dà UB se modificato

    Quindi quando scrivi:

     char *c = "abc"; 

    Questo è simile a:

     /* __unnamed is magic because modifying it gives UB. */ static char __unnamed[] = "abc"; char *c = __unnamed; 

    Notare il cast implicito da char[] a char * , 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 un array di durata e durata di memorizzazione statica sufficiente per contenere la sequenza. Per i letterali delle stringhe di caratteri, gli elementi dell’array hanno carattere char e sono 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 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 gli oggetti “semplici” dell’array char s e t cui elementi sono inizializzati con 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 tipo “pointer to char” e lo inizializza per puntare a un object con tipo “array of char” con lunghezza 4 i cui elementi sono inizializzati con una stringa di caratteri letterale. Se si tenta di usare p per modificare il contenuto dell’array, il comportamento non è definito.

Implementazione di GCC 4.8 x86-64 ELF

Programma:

 #include  int main() { 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 nella sezione .rodata , 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 (relativo a %rbp ).

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

 readelf -l a.out 

che contiene:

  Section to Segment mapping: Segment Sections... 02 .text .rodata 
 char s[] = "hello"; 

dichiara che s è un array di char che è abbastanza lungo da contenere l’inizializzatore (5 + 1 char ) e inizializza l’array copiando i membri della stringa letterale specificata nell’array.

 char *s = "hello"; 

dichiara s di essere un puntatore a uno o più (in questo caso più) char e lo punta direttamente in una posizione fissa (di sola lettura) contenente il letterale "hello" .

 char s[] = "Hello world"; 

Qui, s è una matrice di caratteri, che può essere sovrascritta se lo si desidera.

 char *s = "hello"; 

Una stringa letterale è usata per creare questi blocchi di caratteri da qualche parte nella memoria a cui punta questo puntatore. Possiamo qui riassegnare l’object a cui punta cambiando, ma finché punta a una stringa letterale il blocco di caratteri a cui punta non può essere modificato.

In aggiunta, considera che, come per gli scopi di sola lettura, l’uso di entrambi è identico, puoi accedere a un carattere indicizzando con il formato [] o *( + ) :

 printf("%c", x[1]); //Prints r 

E:

 printf("%c", *(x + 1)); //Prints r 

Ovviamente, se provi a farlo

 *(x + 1) = 'a'; 

Probabilmente otterrai un errore di segmentazione, mentre stai tentando di accedere alla memoria di sola lettura.

Solo per aggiungere: ottieni anche valori diversi per le loro dimensioni.

 printf("sizeof s[] = %zu\n", sizeof(s)); //6 printf("sizeof *s = %zu\n", sizeof(s)); //4 or 8 

Come accennato in precedenza, per un array '\0' verrà assegnato l’elemento finale.

 char *str = "Hello"; 

Il precedente set str per puntare al valore letterale “Hello” che è hardcoded nell’immagine binaria del programma, che è contrassegnato come read-only in memoria, significa che qualsiasi modifica in questo letterale String è illegale e che genererebbe errori di segmentazione.

 char str[] = "Hello"; 

copia la stringa nella memoria appena allocata nello stack. In questo modo qualsiasi modifica è consentita e legale.

 means str[0] = 'M'; 

cambierà lo str in “Mello”.

Per maggiori dettagli, passare alla domanda simile:

Perché ottengo un errore di segmentazione durante la scrittura su una stringa inizializzata con “char * s” ma non “char s []”?

In caso di:

 char *x = "fred"; 

x è un lvalue – può essere assegnato a. Ma nel caso di:

 char x[] = "fred"; 

x non è un lvalue, è un valore rval – non puoi assegnarlo.

Alla luce dei commenti qui dovrebbe essere ovvio che: char * s = “ciao”; È una ctriggers idea e dovrebbe essere usato in ambito molto ristretto.

Questa potrebbe essere una buona opportunità per sottolineare che “la correttezza costante” è una “buona cosa”. Quando e dove puoi, usa la parola chiave “const” per proteggere il tuo codice, da “rilassati” chiamanti o programmatori, che di solito sono più “rilassati” quando i puntatori entrano in gioco.

Basta il melodramma, ecco cosa si può ottenere quando adornano i puntatori con “const”. (Nota: è necessario leggere le dichiarazioni dei puntatori da destra a sinistra.) Ecco i 3 diversi modi per proteggersi quando si gioca con i puntatori:

 const DBJ* p means "p points to a DBJ that is const" 

– cioè, l’object DBJ non può essere modificato tramite p.

 DBJ* const p means "p is a const pointer to a DBJ" 

– ovvero, è ansible modificare l’object DBJ tramite p, ma non è ansible modificare il puntatore p stesso.

 const DBJ* const p means "p is a const pointer to a const DBJ" 

– cioè, non è ansible modificare il puntatore p stesso, né è ansible modificare l’object DBJ tramite p.

Gli errori relativi alle mutazioni costanti tentate vengono catturati in fase di compilazione. Non c’è spazio di runtime o penalità di velocità per const.

(Supponiamo che stai usando il compilatore C ++, ovviamente?)

–DBJ