string make - Qual è la differenza tra char s [] e char * s?





data type (11)


Tiraggio C99 N1256

Esistono due diversi usi dei letterali stringa 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 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:

    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 oggetto 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 <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 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

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.




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




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.




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

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




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'oggetto a cui punta cambiando, ma finché punta a una stringa letterale il blocco di caratteri a cui punta non può essere modificato.




Alla luce dei commenti qui dovrebbe essere ovvio che: char * s = "ciao"; È una cattiva 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'oggetto DBJ non può essere modificato tramite p.

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

- ovvero, è possibile modificare l'oggetto DBJ tramite p, ma non è possibile modificare il puntatore p stesso.

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

- cioè, non è possibile modificare il puntatore p stesso, né è possibile modificare l'oggetto 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




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 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 *(<var> + <index>) :

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.




Questa dichiarazione:

char s[] = "hello";

Crea un oggetto - 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).




Non c'è alcuna differenza tra i due - string, tuttavia, sembra essere l'opzione preferita quando si considera il codice sorgente di altri sviluppatori.







c string char constants