C: differenze tra puntatore char e array

Prendere in considerazione:

char amessage[] = "now is the time"; char *pmessage = "now is the time"; 

Ho letto da The C Programming Language , 2nd Edition che le due dichiarazioni precedenti non fanno la stessa cosa.

Ho sempre pensato che un array sia un modo conveniente per manipolare i puntatori per memorizzare alcuni dati, ma questo non è assolutamente il caso … Quali sono le differenze “non banali” tra array e puntatori in C?

Vero, ma è una sottile differenza. Essenzialmente, il primo:

 char amessage[] = "now is the time"; 

Definisce una matrice i cui membri vivono nello spazio di stack dell’ambito corrente, mentre:

 char *pmessage = "now is the time"; 

Definisce un puntatore che vive nello spazio di stack dell’ambito corrente, ma che fa riferimento alla memoria altrove (in questo caso, “ora è il momento” è memorizzato altrove nella memoria, comunemente una tabella di stringhe).

Inoltre, si noti che poiché i dati appartenenti alla seconda definizione (il puntatore esplicito) non sono memorizzati nello spazio di stack dell’ambito corrente, non sono specificati esattamente dove verranno memorizzati e non dovrebbero essere modificati.

Modifica: Come sottolineato da Mark, GMan e Pavel, c’è anche una differenza quando l’operatore address-of viene utilizzato su una di queste variabili. Ad esempio, & pmessage restituisce un puntatore di tipo char ** o un puntatore a un puntatore ai caratteri, mentre & amessage restituisce un puntatore di tipo char (*) [16] o un puntatore a un array di 16 caratteri (che, come un char **, deve essere sottoposto a dereferenziazione due volte, come indicato da litb).

Ecco un’ipotetica mappa della memoria, che mostra i risultati delle due dichiarazioni:

  0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x00008000: 'n' 'o' 'w' ' ' 'i' 's' ' ' 't' 0x00008008: 'h' 'e' ' ' 't' 'i' 'm' 'e' '\0' ... amessage: 0x00500000: 'n' 'o' 'w' ' ' 'i' 's' ' ' 't' 0x00500008: 'h' 'e' ' ' 't' 'i' 'm' 'e' '\0' pmessage: 0x00500010: 0x00 0x00 0x80 0x00 

La stringa letterale “now is the time” è memorizzata come una matrice di caratteri a 16 elementi all’indirizzo di memoria 0x00008000. Questa memoria potrebbe non essere scrivibile; è meglio presumere che non lo è. Non si dovrebbe mai tentare di modificare il contenuto di una stringa letterale.

La dichiarazione

 char amessage[] = "now is the time"; 

alloca una matrice di caratteri di 16 elementi all’indirizzo di memoria 0x00500000 e copia il contenuto della stringa letterale su di esso. Questo ricordo è scrivibile; puoi cambiare il contenuto dell’amessage in base al tuo cuore:

 strcpy(amessage, "the time is now"); 

La dichiarazione

 char *pmessage = "now is the time"; 

assegna un singolo puntatore al char all’indirizzo di memoria 0x00500010 e copia l’ indirizzo della stringa letterale su di esso.

Poiché pmessage punta alla stringa letterale, non dovrebbe essere usato come argomento per le funzioni che devono modificare il contenuto della stringa:

 strcpy(amessage, pmessage); /* OKAY */ strcpy(pmessage, amessage); /* NOT OKAY */ strtok(amessage, " "); /* OKAY */ strtok(pmessage, " "); /* NOT OKAY */ scanf("%15s", amessage); /* OKAY */ scanf("%15s", pmessage); /* NOT OKAY */ 

e così via. Se hai modificato pmessage per indicare un messaggio di errore:

 pmessage = amessage; 

quindi può essere usato ovunque si possa usare l’amessage.

Una matrice contiene gli elementi. Un puntatore indica loro.

Il primo è una forma breve di dire

 char amessage[16]; amessage[0] = 'n'; amessage[1] = 'o'; ... amessage[15] = '\0'; 

Cioè, è un array che contiene tutti i personaggi. L’inizializzazione speciale lo inizializza automaticamente e determina automaticamente la dimensione. Gli elementi dell’array sono modificabili: puoi sovrascrivere i caratteri al suo interno.

La seconda forma è un puntatore, che punta solo ai personaggi. Memorizza i caratteri non direttamente. Poiché la matrice è una stringa letterale, non è ansible prendere il puntatore e scrivere dove punta

 char *pmessage = "now is the time"; *pmessage = 'p'; /* undefined behavior! */ 

Probabilmente questo codice si bloccherà sulla tua scatola. Ma può fare tutto ciò che vuole, perché il suo comportamento non è definito.

Non posso aggiungere utilmente alle altre risposte, ma osserverò che in Deep C Secrets , Peter van der Linden tratta questo esempio in dettaglio. Se stai facendo questo tipo di domande penso che amerai questo libro.


PS Puoi assegnare un nuovo valore a pmessage . Non è ansible assegnare un nuovo valore amessage ; è immutabile .

Se una matrice è definita in modo che la sua dimensione sia disponibile al momento della dichiarazione, sizeof(p)/sizeof(type-of-array) restituirà il numero di elementi nella matrice.

Insieme alla memoria per la stringa “ora è il tempo” che viene allocata in due luoghi diversi, si dovrebbe anche tenere presente che il nome dell’array agisce come un valore puntatore invece di una variabile puntatore che è pmessage. La differenza principale è che la variabile puntatore può essere modificata in modo da puntare altrove e l’array non può.

 char arr[] = "now is the time"; char *pchar = "later is the time"; char arr2[] = "Another String"; pchar = arr2; //Ok, pchar now points at "Another String" arr = arr2; //Compiler Error! The array name can be used as a pointer VALUE //not a pointer VARIABLE 

Un puntatore è solo una variabile che contiene un indirizzo di memoria. Si noti che si sta giocando con “string letterals” che è un altro problema. Differenze spiegate in linea: in sostanza:

 #include  int main () { char amessage[] = "now is the time"; /* Attention you have created a "string literal" */ char *pmessage = "now is the time"; /* You are REUSING the string literal */ /* About arrays and pointers */ pmessage = NULL; /* All right */ amessage = NULL; /* Compilation ERROR!! */ printf ("%d\n", sizeof (amessage)); /* Size of the string literal*/ printf ("%d\n", sizeof (pmessage)); /* Size of pmessage is platform dependent - size of memory bus (1,2,4,8 bytes)*/ printf ("%p, %p\n", pmessage, &pmessage); /* These values are different !! */ printf ("%p, %p\n", amessage, &amessage); /* These values are THE SAME!!. There is no sense in retrieving "&amessage" */ /* About string literals */ if (pmessage == amessage) { printf ("A string literal is defined only once. You are sharing space"); /* Demostration */ "now is the time"[0] = 'W'; printf ("You have modified both!! %s == %s \n", amessage, pmessage); } /* Hope it was useful*/ return 0; } 

La prima forma ( amessage ) definisce una variabile (una matrice) che contiene una copia della stringa "now is the time" .

Il secondo modulo ( pmessage ) definisce una variabile (un puntatore) che vive in una posizione diversa rispetto a qualsiasi copia della stringa "now is the time" .

Prova questo programma:

 #include  #include  int main (int argc, char *argv []) { char amessage [] = "now is the time"; char *pmessage = "now is the time"; printf("&amessage : %#016"PRIxPTR"\n", (uintptr_t)&amessage); printf("&amessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&amessage[0]); printf("&pmessage : %#016"PRIxPTR"\n", (uintptr_t)&pmessage); printf("&pmessage[0]: %#016"PRIxPTR"\n", (uintptr_t)&pmessage[0]); printf("&\"now is the time\": %#016"PRIxPTR"\n", (uintptr_t)&"now is the time"); return 0; } 

Vedrai che mentre &amessage è uguale a &amessage[0] , questo non è vero per &pmessage e &pmessage[0] . In effetti, vedrai che la stringa memorizzata in un amessage presente nello stack, mentre la stringa puntata da pmessage vive altrove.

L’ultima stampa mostra l’indirizzo della stringa letterale. Se il compilatore esegue il “pooling di stringhe”, allora ci sarà una sola copia della stringa “ora è l’ora” – e vedrai che il suo indirizzo non è lo stesso di quello di un amessage . Questo perché amessage ottiene una copia della stringa quando viene inizializzata.

Alla fine, il punto è che l’ amessage memorizza la stringa nella propria memoria (in pila, in questo esempio), mentre il pmessage indica la stringa che è memorizzata altrove.

Il secondo alloca la stringa in alcune sezioni di sola lettura dell’ELF. Prova quanto segue:

 #include  int main(char argc, char** argv) { char amessage[] = "now is the time"; char *pmessage = "now is the time"; amessage[3] = 'S'; printf("%s\n",amessage); pmessage[3] = 'S'; printf("%s\n",pmessage); } 

e otterrete un segfault sul secondo compito (pmessage [3] = ‘S’).

differenze tra puntatore char e 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 

Le risposte sopra devono aver risposto alla tua domanda. Ma mi piacerebbe suggerirti di leggere il paragrafo “Embryon C” in The Development of C Language scritto da Sir Dennis Ritchie.

Per questa riga: char amessage [] = “now is the time”;

il compilatore valuterà gli usi di amessage come un puntatore all’inizio della matrice che contiene i caratteri “ora è il momento”. Il compilatore alloca la memoria per “ora è il momento” e lo inizializza con la stringa “ora è il momento”. Sapete dove viene archiviato quel messaggio perché il messaggio si riferisce sempre all’inizio di quel messaggio. a un messaggio non può essere assegnato un nuovo valore, non è una variabile, è il nome della stringa “ora è il momento”.

Questa riga: char * pmessage = “now is the time”;

dichiara una variabile, pmessage che è inizializzata (dato un valore iniziale) dell’indirizzo iniziale della stringa “now is the time”. A differenza di un messaggio, a pmessage può essere assegnato un nuovo valore. In questo caso, come nel caso precedente, il compilatore memorizza anche “ora è il momento” altrove nella memoria. Ad esempio, questo farà sì che pmessage punti alla “i” che inizia “è il momento”. pmessage = pmessage + 4;

Ecco il mio sumrio delle principali differenze tra array e puntatori, che ho fatto per me:

 //ATTENTION: //Pointer depth 1 int marr[] = {1,13,25,37,45,56}; // array is implemented as a Pointer TO THE FIRST ARRAY ELEMENT int* pmarr = marr; // don't use & for assignment, because same pointer depth. Assigning Pointer = Pointer makes them equal. So pmarr points to the first ArrayElement. int* point = (marr + 1); // ATTENTION: moves the array-pointer in memory, but by sizeof(TYPE) and not by 1 byte. The steps are equal to the type of the array-elements (here sizeof(int)) //Pointer depth 2 int** ppmarr = &pmarr; // use & because going one level deeper. So use the address of the pointer. //TYPES //array and pointer are different, which can be seen by checking their types std::cout << "type of marr is: " << typeid(marr).name() << std::endl; // int* so marr gives a pointer to the first array element std::cout << "type of &marr is: " << typeid(&marr).name() << std::endl; // int (*)[6] so &marr gives a pointer to the whole array std::cout << "type of pmarr is: " << typeid(pmarr).name() << std::endl; // int* so pmarr gives a pointer to the first array element std::cout << "type of &pmarr is: " << typeid(&pmarr).name() << std::endl; // int** so &pmarr gives a pointer to to pointer to the first array elelemt. Because & gets us one level deeper. 

Un array è un puntatore const. Non è ansible aggiornare il suo valore e farlo puntare altrove. Mentre per un puntatore si può fare.