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).
ha ... non sapeva che lo fa. Potrei giurare che c'erano identici. – bolov
@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. –
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. –