2015-05-29 4 views
17

Ispirato a this question.Letterali stringa vs matrice di caratteri durante l'inizializzazione di un puntatore

Siamo in grado di inizializzare un puntatore char da una stringa letterale:

char *p = "ab"; 

ed è perfettamente bene. Si potrebbe pensare che sia equivalente al seguente:

char *p = {'a', 'b', '\0'}; 

Ma a quanto pare non è il caso. E non solo perché i letterali stringa sono memorizzati in una memoria di sola lettura, ma sembra che anche attraverso la stringa letterale ha un tipo di array char e l'inizializzatore {...} ha il tipo di array char, due dichiarazioni vengono gestite in modo diverso, come il compilatore sta dando l'avvertimento:

avvertimento: elementi in eccesso a scalare initializer

nel secondo caso. Qual è la spiegazione di un simile comportamento?

Aggiornamento:

Inoltre, in quest'ultimo caso il puntatore p avrà il valore di 0x61 (il valore del primo elemento dell'array 'a') invece di una locazione di memoria, in modo che il compilatore, come avvertito, prendendo solo il primo elemento dell'inizializzatore e assegnandolo a p.

+2

ha ... non sapeva che lo fa. Potrei giurare che c'erano identici. – bolov

+6

@bolov Sì. Questo è il motivo per cui mi piace leggere le domande su SO. A volte sta dimostrando che considerarsi un professionista in qualche modo è un po '* sovrastimato ... Mostrando cose molto semplici. –

+2

Penso che la sintassi di inizializzazione delle stringhe funzioni perché, in ultima analisi, è sostituita dalla sua posizione in memoria e si risolve in un indirizzo; d'altra parte, il compilatore vede un char * p come luogo in cui memorizzare un singolo valore e l'inizializzazione dell'array esplicito implica più di un valore da memorizzare. –

risposta

7

Penso che tu sia confuso perché char *p = "ab"; e char p[] = "ab"; hanno semantica simile, ma significati diversi.

ritengo che quest'ultimo caso (char p[] = "ab";) è da considerare come notazione breve mano per char p[] = {'a', 'b', '\0'}; (inizializza un array con la dimensione determinata dal inizializzatore). In realtà, in questo caso, potresti dire che "ab" non è realmente utilizzato come stringa letterale .

Tuttavia, primo caso (char *p = "ab";) è differente in quanto inizializza semplicemente il puntatore p per puntare al primo elemento del sola lettura stringa letterale"ab".

Spero che tu veda la differenza. Mentre char p[] = "ab"; è rappresentabile come un'inizializzazione come quella che hai descritto, char *p = "ab"; non lo è, poiché i puntatori sono, beh, non array, e inizializzarli con un inizializzatore di array fa qualcosa di completamente diverso (vale a dire dare loro il valore del primo elemento, 0x61 nel tuo Astuccio).

farla breve, C compilatori solo "sostituire" una stringa letterale con un inizializzatore char matrice se è adatto a farlo, cioè esso viene utilizzato per inizializzare una matrice char.

+0

L'OP capisce questo .. In sostanza vuole sapere perché l'elenco di inizializzazione non è interpretato come un valore letterale. – Gopi

+0

@Gopi Quasi, è più come il motivo per cui il letterale non viene interpretato come una lista. –

+0

@EugeneSh .: Mi dispiace. Vedi l'aggiornamento. Spero che ciò che intendevo dire sia più chiaro ora – Mints97

6

Da C99 abbiamo

Una stringa di caratteri letterale è una sequenza di zero o più caratteri multibyte racchiuso in virgolette

Così nella seconda definizione non v'è alcuna stringa letterale come non è tra virgolette. Il puntatore deve essere allocata memoria prima di scrivere qualcosa ad esso o se si vuole andare in lista di inizializzazione quindi

char p[] = {'a','b','\0'}; 

è ciò che si desidera. Fondamentalmente entrambe sono dichiarazioni diverse.

+0

Sì, entrambe le parti sono chiare. Suppongo che la domanda si stia riducendo a "perché il letterale stringa non viene trattato allo stesso modo di un array di caratteri (anche se quest'ultimo è RO)?" Non esiste un tale * tipo * in c per "string letterale" per quanto Lo so. –

+0

@ EugeneSh.perchè array e stringhe letterali sono due cose diverse? – texasbruce

+0

@EugeneSh .: Immagino che sia per presentare un modo per usare facilmente stringhe immutabili? – Mints97

7

Il secondo esempio non è sintatticamente corretto. In C, è possibile utilizzare {'a', 'b', '\0'} per inizializzare un array, ma non un puntatore.

Invece, è possibile utilizzare un composto C99 letterale (disponibile anche in alcuni compilatori come estensione, ad esempio, GCC) in questo modo:

char *p = (char []){'a', 'b', '\0'}; 

Nota che è più potente come l'inizializzazione non è necessariamente null- terminato.

+0

Nitpick: solo sopra 'c99', IMHO. –

+0

Quindi, per quanto ho capito è solo una convenzione di sintassi? –

+0

@EugeneSh. Se quello che intendi è lo zucchero sintassi, non lo è. –

7

I valori letterali stringa hanno uno "magico" stato in C. Sono diversi da qualsiasi altra cosa. Per capire perché, è utile pensare a questo in termini di gestione della memoria. Ad esempio, chiediti, "Dove si trova una stringa letterale memorizzata? Quando viene liberata dalla memoria?" e le cose inizieranno a dare un senso.

Sono a differenza di valori letterali numerici che si traducono facilmente in istruzioni macchina. Per un esempio semplificato, qualcosa di simile:

int x = 123;

... potrebbe tradurre in qualcosa di simile a livello di macchina:

mov ecx, 123

Quando facciamo qualcosa di simile:

const char* str = "hello";

... ora abbiamo un dilemma:

mov ecx, ???

Non c'è necessariamente una certa comprensione nativo dell'hardware di ciò che un multi-byte, stringa di lunghezza variabile è in realtà. Conosce principalmente bit, byte e numeri e ha registri progettati per memorizzare queste cose, tuttavia una stringa è un blocco di memoria che contiene più di questi.

Quindi i compilatori devono generare istruzioni per archiviare il blocco di memoria di quella stringa da qualche parte, e in genere generano istruzioni durante la compilazione del codice per memorizzare quella stringa da qualche parte in un punto accessibile a livello globale (in genere un segmento di memoria di sola lettura o i dati segmento). Potrebbero inoltre combinare più stringhe letterali identiche per essere memorizzate nella stessa area di memoria per evitare la ridondanza. Ora può generare un'istruzione mov/load per caricare l'indirizzo nella stringa letterale e quindi è possibile utilizzarlo indirettamente tramite un puntatore.

Un altro scenario che potrebbe incorrere in questo:

static const char* some_global_ptr = "blah"; 

int main() 
{ 
    if (...) 
    { 
     const char* ptr = "hello"; 
     ... 
     some_global_ptr = ptr; 
    } 
    printf("%s\n", some_global_ptr); 
} 

Naturalmente ptr va fuori portata, ma abbiamo bisogno che la memoria di stringa letterale di indugiare in giro per questo programma di avere un comportamento ben definito. Le stringhe così letterali traducono non solo indirizzi per blocchi di memoria accessibili a livello globale, ma non vengono liberati finché il tuo binario/programma viene caricato/eseguito in modo da non doversi preoccupare della gestione della memoria. [Modifica: escluse potenziali ottimizzazioni: per il programmatore C, non dobbiamo mai preoccuparci della gestione della memoria di una stringa letterale, quindi l'effetto è come se fosse sempre lì].

Ora sugli array di caratteri, le stringhe letterali non sono necessariamente matrici di caratteri, di per sé. In nessun punto del software possiamo catturarli su un valore r dell'array che può darci il numero di byte assegnati usando sizeof. Possiamo indicare esclusivamente alla memoria attraverso char*/const char*

Questo codice effettivamente ci dà una maniglia per una tale varietà senza coinvolgere un puntatore:

char str[] = "hello"; 

Qualcosa di interessante accade qui. Un compilatore di produzione probabilmente applicherà tutti i tipi di ottimizzazioni, ma escludendole, a un livello base tale codice potrebbe creare due blocchi di memoria separati.

Il primo blocco sarà permanente per la durata del programma e conterrà quella stringa letterale, "hello". Il secondo blocco sarà per l'effettivo array str e non è necessariamente persistente. Se abbiamo scritto questo codice all'interno di una funzione, assegneremo la memoria allo stack, coperemo quella stringa letterale nello stack e libereremo la memoria dallo stack quando lo str esce dallo scope. L'indirizzo di str non corrisponde alla stringa letterale, per dirla in un altro modo.

Infine, quando scriviamo qualcosa di simile:

char str[] = {'h', 'e', 'l', 'l', 'o', '\0'}; 

... non è necessariamente equivalente, come in questo caso non ci sono stringhe letterali coinvolti. Ovviamente a un ottimizzatore è permesso fare ogni genere di cose, ma in questo scenario, è possibile che creeremo semplicemente un singolo blocco di memoria (allocato nello stack e liberato dallo stack se ci troviamo all'interno di una funzione) con le istruzioni per spostare tutti questi numeri (caratteri) specificati nello stack.

Così mentre stiamo effettivamente ottenendo lo stesso effetto della versione precedente per quanto riguarda la logica del software, in realtà stiamo facendo qualcosa di leggermente diverso quando non specificiamo una stringa letterale. Ancora una volta, gli ottimizzatori possono riconoscere quando fare qualcosa di diverso può avere lo stesso effetto logico, quindi potrebbero essere fantasiosi qui e rendere questi due effettivamente la stessa cosa in termini di istruzioni della macchina. Ma a parte questo, questo è un codice sottilmente diverso che stiamo scrivendo.

Ultimo ma non meno importante, quando usiamo gli inizializzatori come {...}, il compilatore si aspetta che tu lo assegni ad un valore-l aggregato con memoria che viene allocata e liberata ad un certo punto quando le cose vanno fuori dal campo di applicazione. Ecco perché stai ricevendo l'errore cercando di assegnare una cosa simile a uno scalare (un singolo puntatore).

+0

'Il primo blocco sarà persistente per la durata del programma, e conterrà quella stringa letterale," ciao "' ... qualsiasi compilatore che lo farebbe senza un 'char * otherStr =" ciao "' ovunque altrimenti nel codice sta per sprecare memoria preziosa. – Mints97

+0

Sì, per cercare di semplificare non ho inserito troppi dettagli su come un ottimizzatore potrebbe gestirlo. Volevo principalmente spiegare la differenza da un livello base. Forse dovrei aggiungere qualche altro avvertimento. –

+1

Ty per la spiegazione. Mi è piaciuto leggere tutto questo. – bolov

Problemi correlati