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


506

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 effettivamente in termini di durata della memoria, sia in fase di compilazione che di runtime.



8
char * s = "ciao", qui è possibile puntare un'altra stringa in fase di esecuzione Voglio dire che non è un puntatore costante è possibile assegnare un altro valore in fase di esecuzione p = "Nishant", mentre s [] qui s è un puntatore costante. ..non è possibile riassegnare un'altra stringa ma possiamo assegnare un altro valore di carattere in s [indice].
Nishant Kumar,

Risposte:


541

La differenza qui è quella

char *s = "Hello world";

verrà inserito "Hello world"nelle parti di sola lettura della memoria e facendo sun puntatore a ciò renderà illegale qualsiasi operazione di scrittura su questa memoria.

Mentre fai:

char s[] = "Hello world";

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

s[0] = 'J';

legale.


22
La stringa letterale "Hello world"è in "parti della memoria di sola lettura" in entrambi gli esempi. L'esempio con l'array punta lì, l'esempio con l'array copia i caratteri negli elementi dell'array.
pm

28
pmg: Nel secondo caso la stringa letterale non esiste necessariamente nella memoria come un singolo oggetto contiguo - è solo un inizializzatore, il compilatore potrebbe emettere una serie di istruzioni "carica byte immediato" che contengono i valori dei caratteri incorporati all'interno loro.
Caf

10
L'esempio di array di caratteri non posiziona necessariamente la stringa nello stack: se appare a livello di file, probabilmente si troverà invece in una sorta di segmento di dati inizializzato.
Caf

9
Vorrei sottolineare che char s = "xx" non deve essere nella memoria di sola lettura (alcune implementazioni non hanno MMU, ad esempio). La bozza n1362 c1x afferma semplicemente che la modifica di un tale array provoca un comportamento indefinito. Ma +1 comunque, poiché fare affidamento su quel comportamento è una cosa sciocca da fare.
paxdiablo,

3
Ottengo una compilazione pulita su un file contenente solo char msg[] = "hello, world!"; la stringa finisce nella sezione dati inizializzata. Quando dichiarato char * constdi finire nella sezione dati di sola lettura. gcc-4.5.3
gcbenison,

152

Prima di tutto, negli argomenti delle funzioni, 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 un array. Dove va la stringa nel primo caso, chiedi? Il compilatore alloca segretamente un array anonimo statico per contenere letteralmente la stringa. Così:

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

Si noti che non è mai necessario tentare di modificare il contenuto di questo array anonimo tramite questo puntatore; gli effetti non sono definiti (spesso significa un arresto anomalo):

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

L'uso della sintassi dell'array lo alloca direttamente nella nuova memoria. Pertanto la modifica è sicura:

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

Tuttavia, l'array rimane attivo solo per il suo ambito contaning, quindi se lo fai in una funzione, non restituire o perdere un puntatore a questo array: crea una copia anziché con strdup()o simile. Se l'array è allocato nell'ambito globale, ovviamente, nessun problema.


72

Questa dichiarazione:

char s[] = "hello";

Crea un oggetto: un chararray di dimensioni 6, chiamato s, inizializzato con i valori 'h', 'e', 'l', 'l', 'o', '\0'. La posizione in cui questo array viene allocato in memoria e la durata della sua durata dipende da dove appare la dichiarazione. Se la dichiarazione è all'interno di una funzione, rimarrà attiva fino alla fine del blocco in cui è dichiarata, e quasi sicuramente sarà allocata nello stack; se è al di fuori di una funzione, verrà probabilmente memorizzato in un "segmento di dati inizializzato" che viene caricato dal file eseguibile nella memoria scrivibile quando il programma viene eseguito.

D'altra parte, questa dichiarazione:

char *s ="hello";

Crea due oggetti:

  • un array di sola lettura di 6 chars contenente i valori 'h', 'e', 'l', 'l', 'o', '\0', che non ha nome e ha una durata di memorizzazione statica (ovvero che vive per l'intera durata 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 e 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 svariabile puntatore in memoria dipende da dove appare la dichiarazione (proprio come nel primo esempio).


1
In entrambe le dichiarazioni per "ciao" la memoria è allocata al momento della congiunzione?. E un'altra cosa char * p = "ciao" qui "ciao" è memorizzato nel segmento di testo come indicato nella risposta ... e che dire di char s [] = "ciao" memorizzerà anche per primo nella parte del segmento di testo e durante il tempo di esecuzione verrà copiato nello stack come indicato da Rickard nella risposta. chiarire questo punto.
Nishant Kumar,

2
@Nishant: Nel char s[] = "hello"caso, "hello"è solo un inizializzatore che dice al compilatore come deve essere inizializzato l'array. Può provocare o meno una stringa corrispondente nel segmento di testo - ad esempio, se sha una durata di memorizzazione statica, è probabile che l'unica istanza di "hello"sarà nel segmento di dati inizializzato - l'oggetto sstesso. Anche se sha una durata di memorizzazione automatica, può essere inizializzato da una sequenza di archivi letterali anziché da una copia (ad es. movl $1819043176, -6(%ebp); movw $111, -2(%ebp)).
Caf

Più precisamente, GCC 4.8 lo inserisce, in .rodatacui lo script del linker scarica nello stesso segmento di .text. Vedere la mia risposta .
Ciro Santilli 5 冠状 病 六四 事件 法轮功

@caf Nella prima risposta di Rickard, è scritto che char s[] = "Hello world";mette la stringa letterale nella memoria di sola lettura e copia la stringa nella memoria appena allocata nello stack. Ma, la risposta parla solo letterale put stringa nella memoria di sola lettura e salta la seconda parte della frase che dice: copies the string to newly allocated memory on the stack. Quindi, la tua risposta è incompleta per non aver specificato la seconda parte?
KPMG

1
@AjaySinghNegi: Come ho affermato in altri commenti (a questa risposta e alla risposta di Rickard), la stringa in char s[] = "Hellow world";è solo un inizializzatore e non è necessariamente memorizzata come copia di sola lettura separata. Se sha una durata di archiviazione statica, è probabile che l'unica copia della stringa si trovi in ​​un segmento di lettura / scrittura nella posizione di s, e anche in caso contrario, il compilatore può scegliere di inizializzare l'array con istruzioni di caricamento immediato o simili invece di copiare da una stringa di sola lettura. Il punto è che in questo caso la stringa di inizializzazione stessa non ha presenza di runtime.
Caf

60

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

Il valore letterale stringa "hello world"è un array di 12 elementi di char( const charin C ++) con durata di archiviazione statica, il che significa che la memoria per esso viene allocata all'avvio del programma e rimane allocata fino al termine del programma. Il tentativo di modificare il contenuto di una stringa letterale invoca un comportamento indefinito.

La linea

char *s0 = "hello world";

definisce s0come un puntatore a charcon durata di memorizzazione automatica (il che significa che la variabile s0esiste solo per l'ambito in cui è dichiarata) e copia l' indirizzo della stringa letterale ( 0x00008000in questo esempio) su di essa. Notare che, poichè s0indica un letterale stringa, non dovrebbe essere usato come argomento per qualsiasi funzione che tentare di modificarlo (ad esempio, strtok(), strcat(), strcpy(), ecc).

La linea

char s1[] = "hello world";

definisce s1come un array di 12 elementi di char(la lunghezza è presa dal letterale stringa) con durata di memorizzazione automatica e copia il contenuto del valore letterale nell'array. Come puoi vedere dalla mappa di memoria, abbiamo due copie della stringa "hello world"; la differenza è che puoi modificare la stringa contenuta in s1.

s0e s1sono 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 s0in modo che punti a una stringa diversa letterale o a un'altra variabile. Non è possibile riassegnare la variabile s1in modo che punti a un array diverso.


2
Penso che l'ipotetica mappa della memoria lo renda facile da capire!
mezzanotte,

32

Bozza 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":

    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, cpuò 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[]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 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 se i tcui 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.

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

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


15
char s[] = "hello";

dichiara sdi essere un array il charcui tempo è sufficiente per contenere l'inizializzatore (5 + 1 chars) e inizializza l'array copiando i membri della stringa data in senso letterale nell'array.

char *s = "hello";

dichiara sdi essere un puntatore a una o più (in questo caso più) charse lo punta direttamente in una posizione fissa (sola lettura) contenente il valore letterale "hello".


1
Quale metodo è preferibile utilizzare nelle funzioni se s non verrà modificato, f (const char s []) o f (const char * s)?
psihodelia,

1
@psihodelia: in una dichiarazione di funzione non c'è differenza. In entrambi i casi sè un puntatore a const char.
CB Bailey,

4
char s[] = "Hello world";

Ecco suna serie di personaggi che possono essere sovrascritti se lo desideriamo.

char *s = "hello";

Una stringa letterale viene utilizzata per creare questi blocchi di caratteri da qualche parte nella memoria a cui spunta questo puntatore . Possiamo qui riassegnare l'oggetto a cui punta cambiandolo, ma fintanto che punta a una stringa letterale il blocco di caratteri a cui punta non può essere modificato.


@bo Persson Perché il blocco di caratteri non può essere modificato nel secondo caso?
Pankaj Mahato,

3

Inoltre, considera che, per scopi di sola lettura, l'uso di entrambi è identico, puoi accedere a un carattere indicizzando []o *(<var> + <index>) formattando:

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, poiché stai tentando di accedere alla memoria di sola lettura.


Questo non è in alcun modo diverso da quello x[1] = 'a';che segfault (ovviamente a seconda della piattaforma).
glglgl,

3

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 come elemento finale.


2
char *str = "Hello";

Quanto sopra imposta str per indicare il valore letterale "Hello" che è codificato nell'immagine binaria del programma, che è contrassegnato come di sola lettura in memoria, significa che qualsiasi cambiamento in questo valore letterale di stringa è illegale e che causerebbe errori di segmentazione.

char str[] = "Hello";

copia la stringa nella memoria appena allocata nello stack. Pertanto, è consentito e legale apportare qualsiasi modifica.

means str[0] = 'M';

cambierà lo str in "Mello".

Per maggiori dettagli, passare attraverso la domanda simile:

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


0

In caso di:

char *x = "fred";

x è un valore - a cui può essere assegnato. Ma nel caso di:

char x[] = "fred";

x non è un valore, è un valore - non è possibile assegnarlo.


3
Tecnicamente, xè un valore non modificabile. In quasi tutti i contesti, tuttavia, verrà valutato come un puntatore al suo primo elemento e quel valore è un valore.
Caf

0
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal

-1

Alla luce dei commenti qui dovrebbe essere ovvio che: char * s = "ciao"; È una cattiva idea e dovrebbe essere utilizzata in un ambito molto ristretto.

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

Basta melodramma, ecco cosa si può ottenere quando si 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" 

- cioè, è 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 puoi cambiare il puntatore p stesso, né puoi cambiare l'oggetto DBJ tramite p.

Gli errori relativi alle tentate mutazioni delle costanti vengono rilevati al momento della compilazione. Non esiste spazio di runtime o penalità di velocità per const.

(Supponiamo che tu stia utilizzando il compilatore C ++, ovviamente?)

--DBJ


È tutto corretto, ma non ha nulla a che fare con la domanda. E per quanto riguarda la tua ipotesi su un compilatore C ++, la domanda è taggata come C, non come C ++.
Fabio dice di reintegrare Monica il

Non c'è niente di male in char * s = "const string";
Paul Smith,
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.