2015-04-26 26 views
5

Sto leggendo Programmazione C - Un approccio moderno di K.N.King per apprendere il linguaggio di programmazione C ed è stato notato che le istruzioni goto non devono saltare dichiarazioni di array di lunghezza variabile.Salta la dichiarazione delle variabili usando goto?

Ma ora la domanda è: perché i salti goto sono consentiti per ignorare le dichiarazioni di array di lunghezza fissa e le dichiarazioni ordinarie? E più precisamente, qual è il comportamento di esempi come questi, secondo lo standard C99? Quando ho provato questi casi sembrava che le dichiarazioni non fossero saltate sopra, ma è corretto? Le variabili le cui dichiarazioni potrebbero essere state superate sono sicure da usare?

1.

goto later; 
int a = 4; 
later: 
printf("%d", a); 

2.

goto later; 
int a; 
later: 
a = 4; 
printf("%d", a); 

3.

goto later; 
int a[4]; 
a[0] = 1; 
later: 
a[1] = 2; 
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++) 
    printf("%d\n", a[i]); 
+0

@ Mints97 Ah, quindi se le istruzioni hanno i propri blocchi anche senza istruzioni composte? Presumo che sia la risposta quindi :) peccato non posso accettare i commenti – MinecraftShamrock

+0

cosa intendi con questo? che cosa fare se le affermazioni hanno a che fare con questo? E i blocchi e le dichiarazioni composte sono più o meno gli stessi, IIRC – Mints97

+0

@ Mints97 Voglio dire che le variabili dichiarate condizionatamente non vengono spostate all'inizio dell'intera funzione ma solo all'inizio del "blocco" condizionale in cui si trovano a destra? Quindi un'istruzione if senza un'istruzione composta rappresenterebbe anche un blocco di questo tipo. La mia comprensione è corretta? – MinecraftShamrock

risposta

9

Sono in vena di spiegare questo senza Memory cruento la i vostri dettagli (credetemi, ottengono molto in cui i VLA sono usati; vedere @ la risposta di Ulfalizer per i dettagli).

Così, in origine, nel C89, era obbligatorio dichiarare tutte le variabili all'inizio di un blocco, come questo:

{ 
    int a = 1; 
    a++; 
    /* ... */ 
} 

Ciò implica direttamente una cosa molto importante: un blocco == un set immutabile di dichiarazioni variabili.

C99 ha cambiato questo. In essa è possibile dichiarare le variabili in qualsiasi parte del blocco, ma le dichiarazioni di dichiarazione sono ancora diverse dalle dichiarazioni regolari.

In effetti, per capire questo, è possibile immaginare che tutte le dichiarazioni di variabili vengano spostate implicitamente all'inizio del blocco in cui vengono dichiarate e rese non disponibili per tutte le istruzioni che le precedono.

Questo è semplicemente perché un blocco == un set di dichiarazioni regola ancora valida.

Ecco perché non è possibile "saltare sopra una dichiarazione". La variabile dichiarata esisterebbe ancora.

Il problema è l'inizializzazione. Non viene "spostato" da nessuna parte. equivalente Così, tecnicamente, per il vostro caso, i seguenti programmi potrebbero essere considerati:

goto later; 
int a = 100; 
later: 
printf("%d", a); 

e

int a; 
goto later; 
a = 100; 
later: 
printf("%d", a); 

Come si può vedere, la dichiarazione è ancora lì, ciò che viene saltato è l'inizializzazione.

Il motivo per cui questo non funziona con gli VLA è che sono diversi.In breve, è perché questo è valido:

int size = 7; 
int test[size]; 

Le dichiarazioni di VLA saranno, a differenza di tutte le altre dichiarazioni, comportarsi in modo diverso nelle diverse parti del blocco in cui sono dichiarate. In effetti, un VLA potrebbe avere layout di memoria completamente diversi a seconda di dove è stato dichiarato. Non puoi "spostarlo" fuori dal luogo in cui sei appena saltato.

Si può chiedere, "va bene, allora perché non farlo in modo che la dichiarazione non sia influenzata dal goto"? Beh, saresti ancora ottiene casi come questo:

goto later; 
int size = 7; 
int test[size]; 
later: 

Che cosa si aspetta in realtà questo per fare ..

Quindi, che vieta che salta sopra dichiarazioni VLA è lì per un motivo - è la più? decisione logica per trattare casi come quelli sopra semplicemente proibendoli del tutto.

+3

Anche in C89/C90, era legale saltare in un blocco: 'goto LABEL ; {int n = 42; ETICHETTA: printf ("% d \ n", n); } ' –

+0

@KeithThompson: woah, grazie! Ho completamente dimenticato che uno =) Modificherò la risposta subito! – Mints97

+1

Un esempio sorprendente/terrificante di ciò che @KeithThompson menziona è noto come [dispositivo di Duff] (https://en.wikipedia.org/wiki/Duff%27s_device). Usa le etichette degli interruttori per saltare nel mezzo di un ciclo while. –

5

Il motivo per cui non è consentito saltare la dichiarazione di un array a lunghezza variabile (VLA) è che sarebbe complicato con il modo in cui i VLA vengono comunemente implementati e complicherebbe la semantica della lingua.

Il modo in cui i VLA sono implementati nella pratica è decrementando (o incrementando, su architetture in cui lo stack cresce) il puntatore dello stack di un importo dinamico (calcolato al runtime) per fare spazio al VLA nello stack. Questo accade nel punto in cui viene dichiarata la VLA (almeno concettualmente, ignorando le ottimizzazioni). Ciò è necessario in modo che le successive operazioni di stack (ad es., Spingendo gli argomenti nello stack per una chiamata di funzione) non passino alla memoria del VLA.

Per i VLA annidati in blocchi, il puntatore dello stack verrebbe generalmente ripristinato alla fine del blocco contenente il VLA. Se a goto è stato concesso di passare a un tale blocco e dopo la dichiarazione di un VLA, il codice per ripristinare il puntatore dello stack sarebbe stato eseguito senza che fosse stato eseguito il corrispondente codice di inizializzazione, il che probabilmente causerebbe problemi. Ad esempio, il puntatore dello stack potrebbe essere incrementato dalla dimensione del VLA anche se non è mai decrementato, il che, tra le altre cose, renderebbe l'indirizzo di ritorno che è stato premuto quando la funzione contenente il VLA è stata chiamata nel posto sbagliato relativo al puntatore dello stack.

È anche disordinato da una prospettiva di semantica del linguaggio puro. Se ti è permesso saltare la dichiarazione, allora quale sarebbe la dimensione della matrice? Cosa deve restituire sizeof? Cosa significa accedervi?

Per i casi non VLA, si salta semplicemente l'inizializzazione del valore (se presente), che non necessariamente causa problemi di per sé. Se si passa a una definizione non VLA come int x;, la memorizzazione verrà comunque riservata anche alla variabile x. Gli VLA sono diversi in quanto la loro dimensione viene calcolata in fase di esecuzione, il che complica le cose.

Come nota a margine, una delle motivazioni per consentire alle variabili di essere dichiarate ovunque all'interno di un blocco in C99 (C89 richiede che le dichiarazioni siano all'inizio del blocco, anche se almeno GCC le consente all'interno del blocco come estensione) doveva supportare gli VLA. Essere in grado di eseguire calcoli prima nel blocco prima di dichiarare la dimensione del VLA è utile.

Per ragioni un po 'correlate, il C++ non consente di saltare le dichiarazioni oggetto (o le inizializzazioni per tipi di dati vecchi e chiari, ad esempio) per gli goto s. Questo perché non sarebbe sicuro saltare il codice che chiama il costruttore ma eseguire il distruttore alla fine del blocco.

3

Utilizzare uno goto per saltare la dichiarazione di una variabile è quasi certamente una pessima idea, ma è perfettamente legale.

C distingue tra la durata di una variabile e la sua portata .

Per una variabile dichiarata senza la parola chiave static all'interno di una funzione, il suo ambito (l'area del testo del programma in cui è visibile il suo nome) si estende dalla definizione alla fine del blocco di chiusura più vicino. La sua durata (durata di conservazione) inizia all'ingresso nel blocco e termina all'uscita dal blocco. Se ha un inizializzatore, viene eseguito quando (e se) viene raggiunta la definizione.

Ad esempio:

{ /* the lifetime of x and y starts here; memory is allocated for both */ 
    int x = 10; /* the name x is visible from here to the "}" */ 
    int y = 20; /* the name y is visible from here to the "}" */ 
    int vla[y]; /* vla is visible, and its lifetime begins here */ 
    /* ... */ 
} 

Per gli array di lunghezza variabile (VLA), la visibilità dell'identificatore è lo stesso, ma la durata dell'oggetto inizia alla definizione. Perché? Perché la lunghezza dell'array non è necessariamente nota prima di quel punto. Nell'esempio, non è possibile allocare memoria per vla all'inizio del blocco, perché non conosciamo ancora il valore di .

A goto quello ignora una definizione di oggetto ignora qualsiasi inizializzatore per quell'oggetto, ma la memoria è ancora allocata per esso. Se goto salta in un blocco, la memoria viene allocata quando viene inserito il blocco. In caso contrario (se sia lo goto sia l'etichetta di destinazione sono allo stesso livello nello stesso blocco), l'oggetto sarà già stato allocato.

... 
goto LABEL; 
{ 
    int x = 10; 
    LABEL: printf("x = %d\n", x); 
} 

Quando si esegue l'istruzione printf, x esiste e il suo nome è visibile, ma la sua inizializzazione è stato bypassato, quindi ha un valore indeterminato.

La lingua proibisce uno goto che salta la definizione di una matrice di lunghezza variabile. Se fosse permesso, salverebbe l'allocazione di memoria per l'oggetto, e qualsiasi tentativo di fare riferimento a esso causerebbe un comportamento indefinito.

goto dichiarazioni do have their uses. Usarli per saltare le dichiarazioni, benché consentito dalla lingua, non è uno di questi.