Definizione della variabile all’interno dell’istruzione switch

Nel seguente codice, perché la variabile non ha assegnato il valore 1 ?

 #include  int main(void) { int val = 0; switch (val) { int i = 1; //i is defined here case 0: printf("value: %d\n", i); break; default: printf("value: %d\n", i); break; } return 0; } 

Quando compilo, ricevo un avvertimento sul fatto di non essere inizializzato nonostante int i = 1; che lo inizializza chiaramente

 $ gcc -Wall test.c warning: 'i' is used uninitialized in this function [-Wuninitialized] printf("value %d\n", i); ^ 

Se val = 0 , l’output è 0 .

Se val = 1 o qualsiasi altra cosa, anche l’output è 0.

Per favore spiegami perché la variabile i è dichiarata ma non definita all’interno dello switch. L’object il cui identificatore è i esiste con durata di archiviazione automatica (all’interno del blocco) ma non viene mai inizializzato. Perché?

Secondo lo standard C (6.8 Dichiarazioni e blocchi), sottolineatura mia:

3 Un blocco consente di raggruppare una serie di dichiarazioni e istruzioni in un’unica unità sintattica. Gli inizializzatori di oggetti con durata di memorizzazione automatica e i dichiaratori di array di lunghezza variabile di identificatori ordinari con ambito di blocco vengono valutati e i valori vengono memorizzati negli oggetti (inclusa la memorizzazione di un valore indeterminato in oggetti senza un inizializzatore) ogni volta che la dichiarazione è raggiunto nell’ordine di esecuzione, come se fosse una dichiarazione, e all’interno di ciascuna dichiarazione nell’ordine in cui appaiono i dichiaranti.

E (6.8.4.2 L’istruzione switch)

4 Un’istruzione switch fa in modo che il controllo salti a , in, o passato l’istruzione che rappresenta il corpo dell’interruttore, a seconda del valore di un’espressione di controllo e sulla presenza di un’etichetta predefinita e dei valori di qualsiasi etichetta di caso sopra o nella cambia corpo. Un caso o etichetta predefinita è accessibile solo all’interno dell’istruzione dell’interruttore di chiusura più vicino.

Quindi l’inizializzatore della variabile i non viene mai valutato perché la dichiarazione

  switch (val) { int i = 1; //i is defined here //... 

non viene raggiunto nell’ordine di esecuzione a causa di salti alle etichette delle maiuscole e come qualsiasi variabile con durata di archiviazione automatica ha un valore indeterminato.

Vedi anche questo esempio normativo dal 6.8.4.2/7:

ESEMPIO Nel frammento del programma artificiale

 switch (expr) { int i = 4; f(i); case 0: i = 17; /* falls through into default code */ default: printf("%d\n", i); } 

l’object il cui identificatore è i esiste con durata di memorizzazione automatica (all’interno del blocco) ma non viene mai inizializzato e, pertanto, se l’espressione di controllo ha un valore diverso da zero, la chiamata alla funzione printf accederà a un valore indeterminato. Allo stesso modo, la chiamata alla funzione f non può essere raggiunta.

Nel caso in cui val non sia zero, l’esecuzione salta direttamente al valore predefinito dell’etichetta. Ciò significa che la variabile i , pur definita nel blocco, non è inizializzata e il suo valore è indeterminato.

6.8.2.4 L’istruzione switch

  1. Un’istruzione switch fa in modo che il controllo salti a, in, o passato l’istruzione che è il corpo dello switch, a seconda del valore di un’espressione di controllo e sulla presenza di un’etichetta predefinita e dei valori di qualsiasi etichetta di caso sull’interruttore o nell’interruttore corpo. Un caso o etichetta predefinita è accessibile solo all’interno dell’istruzione dell’interruttore di chiusura più vicino.

In effetti, il tuo i è dichiarato all’interno del blocco switch , quindi esiste solo all’interno switch . Tuttavia, la sua inizializzazione non viene mai raggiunta, quindi rimane non inizializzata quando val non è 0.

È un po ‘come il seguente codice:

 { int i; if (val==0) goto zerovalued; else goto nonzerovalued; i=1; // statement never reached zerovalued: i = 10; printf("value:%d\n",i); goto next; nonzerovalued: printf("value:%d\n",i); goto next; next: return 0; } 

Intuitivamente, pensate alla dichiarazione grezza come chiedere al compilatore di trovare una posizione (sul frame di chiamata nello stack di chiamate, o in un registro, o qualsiasi altra cosa) e pensare all’inizializzazione come a un’istruzione di assegnazione. Entrambi sono passaggi separati, e si potrebbe guardare una dichiarazione di inizializzazione in C come int i=1; come zucchero sintattico per la dichiarazione grezza int i; seguito dal compito di inizializzazione i=1; .

(in realtà, le cose sono leggermente più complesse ad esempio con int i= i!=i; e ancora più complesse in C ++)

Riga per l’inizializzazione di i variabile int i = 1; non viene mai chiamato perché non appartiene a nessuno dei casi disponibili.

L’inizializzazione delle variabili con durate di archiviazione automatica è dettagliata in C11 6.2.4p6 :

  1. Per un object di questo tipo che non ha un tipo di array a lunghezza variabile, la sua durata si estende dall’ingresso nel blocco a cui è associato fino a quando l’esecuzione di quel blocco non termina in alcun modo. (L’inserimento di un blocco chiuso o il richiamo di una funzione sospende, ma non termina, l’esecuzione del blocco corrente.) Se il blocco viene immesso in modo ricorsivo, ogni volta viene creata una nuova istanza dell’object. Il valore iniziale dell’object è indeterminato. Se viene specificata un’inizializzazione per l’object, viene eseguita ogni volta che viene raggiunta la dichiarazione o il letterale composto nell’esecuzione del blocco; in caso contrario, il valore diventa indeterminato ogni volta che viene raggiunta la dichiarazione.

Cioè la vita di i in

 switch(a) { int i = 2; case 1: printf("%d",i); break; default: printf("Hello\n"); } 

è da { a } . Il suo valore è indeterminato , a meno che la dichiarazione int i = 2; è raggiunto nell’esecuzione del blocco . Poiché la dichiarazione è prima di qualsiasi etichetta di caso, la dichiarazione non può essere mai raggiunta, poiché il switch all’etichetta del caso corrispondente – e all’inizializzazione.

Quindi i non inizializzato. E poiché funziona, e poiché ha il suo indirizzo mai preso, l’uso del valore non inizializzato per il comportamento non definito C11 6.3.2.1p2 :

  1. […] Se il lvalue designa un object di durata di archiviazione automatica che avrebbe potuto essere dichiarato con la class di archiviazione del registro (non ha mai avuto l’indirizzo preso), e quell’object non è inizializzato (non dichiarato con un inizializzatore e nessun assegnamento a esso ha stato eseguito prima dell’uso), il comportamento non è definito.

(Si noti che lo stesso standard qui riporta i contenuti nella parentesi chiarificante in modo errato – viene dichiarato con un inizializzatore ma l’inizializzatore non viene eseguito).