Qual è la differenza tra char array e char pointer in C?

Sto cercando di capire i puntatori in C ma attualmente sono confuso con quanto segue:

Qual è la differenza quando passo entrambe queste variabili in questa funzione?

 void printSomething(char *p) { printf("p: %s",p); } 

char* e char[] sono tipi diversi , ma non è immediatamente evidente in tutti i casi. Questo perché gli array decadono in puntatori , il che significa che se viene fornita un’espressione di tipo char[] dove è previsto uno di tipo char* , il compilatore converte automaticamente l’array in un puntatore al suo primo elemento.

La tua funzione di esempio printSomething aspetta un puntatore, quindi se provi a passare un array in questo modo:

 char s[10] = "hello"; printSomething(s); 

Il compilatore fa finta di aver scritto questo:

 char s[10] = "hello"; printSomething(&s[0]); 

Vediamo:

 #include  #include  int main() { char *p = "hello"; char q[] = "hello"; // no need to count this printf("%zu\n", sizeof(p)); // => size of pointer to char -- 4 on x86, 8 on x86-64 printf("%zu\n", sizeof(q)); // => size of char array in memory -- 6 on both // size_t strlen(const char *s) and we don't get any warnings here: printf("%zu\n", strlen(p)); // => 5 printf("%zu\n", strlen(q)); // => 5 return 0; } 

foo * e foo [] sono tipi diversi e vengono gestiti in modo diverso dal compilatore (puntatore = indirizzo + rappresentazione del tipo del puntatore, array = puntatore + lunghezza facoltativa dell’array, se noto, ad esempio, se l’array è assegnato staticamente ), i dettagli possono essere trovati nello standard. E a livello del tempo di esecuzione nessuna differenza tra loro (in assembler, beh, quasi, vedi sotto).

Inoltre, c’è una domanda correlata nelle FAQ C :

D : Qual è la differenza tra queste inizializzazioni?

 char a[] = "string literal"; char *p = "string literal"; 

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 doppia citazione in C source) può essere utilizzata in due modi leggermente diversi:

  1. Come inizializzatore per un array di caratteri, come nella dichiarazione di char a [], specifica i valori iniziali dei caratteri di quell’array (e, se necessario, le sue dimensioni).
  2. In qualsiasi altro luogo, si trasforma in una matrice di caratteri non denominata e statici, e questo array senza nome può essere memorizzato nella memoria di sola lettura e, pertanto, non può essere necessariamente modificato. In un contesto di espressione, la matrice viene convertita immediatamente in un puntatore, come al solito (vedere la sezione 6), quindi la seconda dichiarazione inizializza p per puntare al primo elemento dell’array senza nome.

Alcuni compilatori hanno uno switch che controlla se i letterali stringa sono scrivibili o meno (per compilare il vecchio codice), e alcuni possono avere opzioni per far sì che i valori letterali stringa vengano trattati formalmente come array di const char (per una migliore cattura degli errori).

Vedi anche le domande 1.31, 6.1, 6.2, 6.8 e 11.8b.

Riferimenti: K & R2 sec. 5,5 p. 104

ISO Sec. 6.1.4, Sez. 6.5.7

Motivazione sec. 3.1.4

H & S Sec. 2,7,4 pp. 31-2

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 

Non ti è permesso cambiare il contenuto di una costante di stringa, che è ciò a cui punta la prima p . Il secondo p è un array inizializzato con una costante di stringa, e puoi cambiarne il contenuto.

Per casi come questo, l’effetto è lo stesso: si finisce per passare l’indirizzo del primo carattere in una stringa di caratteri.

Le dichiarazioni ovviamente non sono le stesse però.

Quanto segue mette da parte la memoria per una stringa e anche un puntatore di caratteri, e quindi inizializza il puntatore in modo che punti al primo carattere nella stringa.

 char *p = "hello"; 

Mentre il seguente mette da parte la memoria solo per la stringa. Quindi può effettivamente usare meno memoria.

 char p[10] = "hello"; 

Per quanto posso ricordare, un array è in realtà un gruppo di puntatori. Per esempio

 p[1]== *(&p+1) 

è una vera affermazione

char p[3] = "hello" ? dovrebbe essere char p[6] = "hello" ricorda che c’è un carattere “\ 0” alla fine di una “stringa” in C.

comunque, l’array in C è solo un puntatore al primo object di un object regolare nella memoria. le uniche differenze sono nella semantica. mentre è ansible modificare il valore di un puntatore per puntare a una diversa posizione nella memoria, una matrice, dopo la creazione, punterà sempre alla stessa posizione.
anche quando si usa la matrice, “nuovo” e “cancella” vengono automaticamente eseguiti automaticamente.