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


216

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

  • char *p = "hello"

    Questo è un puntatore carattere che punta alla matrice di caratteri, a partire da h .

  • char p[] = "hello"

    Questo è un array che memorizza ciao .

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

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

5
Ciò non sarebbe valido: char p[3] = "hello";la stringa di inizializzazione è troppo lunga per la dimensione dell'array dichiarata. Errore di battitura?
Cody Grey

16
O char p[]="hello";sarebbe sufficiente!
deepdive,


1
possibile duplicato di Qual è la differenza tra char s [] e char * s in C? È vero, questo chiede anche in modo specifico il parametro della funzione, ma questo non è charspecifico.
Ciro Santilli 5 冠状 病 六四 事件 法轮功 il

1
devi capire che sono fondamentalmente diversi. l'unica cosa comune in questo è che la base di arry p [] è un puntatore const che ha permesso di accedere all'array p [] tramite un puntatore. p [] stesso contiene memoria per una stringa, mentre * p indica solo l'indirizzo del primo elemento di ONE ONE (ovvero, indica la base della stringa già allocata). Per illustrare meglio questo, considerare di seguito: char * cPtr = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> questo è un errore, in quanto cPtr è un puntatore solo a un carattere char cBuff [] = {'h', 'e', ​​'l', 'l', 'o', '\ 0'}; ==> Questo è ok, bcos cBuff stesso è un array di caratteri
Ilavarasan,

Risposte:


222

char*e char[] sono tipi diversi , ma non è immediatamente evidente in tutti i casi. Questo perché le matrici si decompongono in puntatori , il che significa che se char[]viene fornita un'espressione di tipo dove una di tipochar* , il compilatore converte automaticamente l'array in un puntatore al suo primo elemento.

La tua funzione di esempio printSomethingprevede un puntatore, quindi se provi a passargli un array in questo modo:

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

Il compilatore finge di aver scritto questo:

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

È cambiato qualcosa dal 2012 ad oggi. Per una matrice di caratteri "s" stampa l'intera matrice .. vale a dire, "ciao"
Bhanu Tez,

@BhanuTez No, il modo in cui i dati vengono archiviati e ciò che viene fatto con i dati sono problemi separati. In questo esempio viene stampata l'intera stringa perché in questo modo viene printfgestita la %sstringa di formato: iniziare dall'indirizzo fornito e continuare fino a trovare un terminatore null. Se si desidera stampare solo un carattere, è possibile utilizzare la %cstringa di formato, ad esempio.
iX3,

Volevo solo chiedere se char *p = "abc";il carattere NULL \0viene aggiunto automaticamente come nel caso dell'array char []?
KPMG,

perché posso impostare char *name; name="123";ma posso fare lo stesso con il inttipo? E dopo aver usato %cper la stampa name, l'uscita è stringhe illeggibile: ?
TomSawyer il

83

Vediamo:

#include <stdio.h>
#include <string.h>

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 sono gestiti in modo diverso dal compilatore (puntatore = indirizzo + rappresentazione del tipo di puntatore, matrice = puntatore + lunghezza opzionale della matrice, se noto, ad esempio, se la matrice è allocata staticamente ), i dettagli sono disponibili nella norma. E a livello di runtime nessuna differenza tra loro (in assemblatore, beh, quasi, vedi sotto).

Inoltre, c'è una domanda correlata nelle FAQ di 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 : Un letterale stringa (il termine formale per una stringa tra virgolette doppie in sorgente C) può essere usato in due modi leggermente diversi:

  1. Come inizializzatore per una matrice di caratteri, come nella dichiarazione di caratteri a [], specifica i valori iniziali dei caratteri in quella matrice (e, se necessario, le sue dimensioni).
  2. In qualsiasi altro luogo, si trasforma in una matrice di caratteri statica senza nome e questa matrice senza nome può essere memorizzata in memoria di sola lettura e che pertanto non può essere necessariamente modificata. In un contesto di espressione, l'array viene convertito immediatamente in un puntatore, come al solito (vedere la sezione 6), quindi la seconda dichiarazione inizializza p in modo che punti al primo elemento dell'array senza nome.

Alcuni compilatori hanno un interruttore che controlla se i letterali di stringa sono scrivibili o meno (per compilare il vecchio codice), e alcuni possono avere opzioni per far sì che i letterali di stringa vengano trattati formalmente come array di const char (per una migliore rilevazione 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

Razionale Sec. 3.1.4

H&S Sec. 2.7.4 pp. 31-2


In sizeof (q), perché q non decade in un puntatore, come menziona @Jon nella sua risposta?
garyp,

@garyp q non decade in un puntatore perché sizeof è un operatore, non una funzione (anche se sizeof era una funzione, q decadrebbe solo se la funzione si aspettava un puntatore char).
GiriB,

grazie, ma printf ("% u \ n" invece di printf ("% zu \ n", penso che dovresti rimuovere z.
Zakaria,

33

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

Bozza C99 N1256

Esistono due diversi usi dei letterali stringa di caratteri:

  1. Inizializza char[]:

    char c[] = "abc";      

    Questo è "più magico", e descritto al 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.

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

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

2
@ leszek.hanusz non definito Comportamento stackoverflow.com/questions/2766731/... Google "linguaggio C UB" ;-)
Ciro Santilli郝海东冠状病六四事件法轮功

9

Non puoi modificare il contenuto di una costante di stringa, che è ciò a cui ppunta il primo . Il secondo pè un array inizializzato con una costante di stringa e puoi cambiarne il contenuto.


6

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

Le dichiarazioni ovviamente non sono uguali.

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

char *p = "hello";

Mentre quanto segue mette da parte la memoria solo per la stringa. Quindi può effettivamente utilizzare meno memoria.

char p[10] = "hello";

codeplusplus.blogspot.com/2007/09/… "Tuttavia, l'inizializzazione della variabile richiede prestazioni e penalità di spazio enormi per l'array"
leef

@leef: penso che dipenda da dove si trova la variabile. Se è nella memoria statica, penso che sia possibile che l'array e i dati siano archiviati nell'immagine EXE e non richiedano alcuna inizializzazione. Altrimenti, sì, può certamente essere più lento se i dati devono essere allocati e quindi i dati statici devono essere copiati.
Jonathan Wood

3

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

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

è una vera affermazione


2
Descriverei un array come un puntatore all'indirizzo di un blocco di memoria. Quindi perché *(arr + 1)ti porta al secondo membro di arr. Se *(arr)punta a un indirizzo di memoria a 32 bit, ad esempio bfbcdf5e, quindi *(arr + 1)punta a bfbcdf60(il secondo byte). Quindi perché uscire dall'ambito di un array porterà a strani risultati se il sistema operativo non segfault. Se int a = 24;è all'indirizzo bfbcdf62, l'accesso arr[2]potrebbe tornare 24, supponendo che un segfault non avvenga per primo.
Braden Best

3

Da APUE , sezione 5.14:

char    good_template[] = "/tmp/dirXXXXXX"; /* right way */
char    *bad_template = "/tmp/dirXXXXXX";   /* wrong way*/

... Per il primo modello, il nome viene allocato nello stack, perché utilizziamo una variabile array. Per il secondo nome, tuttavia, utilizziamo un puntatore. In questo caso, solo la memoria per il puntatore stesso risiede nello stack; il compilatore organizza la memorizzazione della stringa nel segmento di sola lettura dell'eseguibile. Quando la mkstempfunzione tenta di modificare la stringa, si verifica un errore di segmentazione.

Il testo citato corrisponde alla spiegazione di @Ciro Santilli.


1

char p[3] = "hello"? dovresti char p[6] = "hello"ricordare che c'è un carattere '\ 0' alla fine di una "stringa" in C.

comunque, l'array in C è solo un puntatore al primo oggetto di un oggetto di regolazione in memoria. le uniche s diverse sono in semantica. mentre è possibile modificare il valore di un puntatore in modo che punti a una diversa posizione nella memoria, una matrice, dopo la creazione, punterà sempre alla stessa posizione.
anche quando si utilizza l'array, il "nuovo" e "elimina" vengono eseguiti automaticamente.

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.