2013-06-13 8 views
6

Quando insegno C, a volte conto su GCC per fare la parte "convincente" di alcune regole. Ad esempio, non si dovrebbe considerare che una variabile locale su una funzione mantenga il valore tra le chiamate.Le variabili locali non sono statiche, ma perché ottengo questo comportamento?

GCC mi ha sempre aiutato a insegnare queste lezioni agli studenti, mettendo la spazzatura su variabili locali, in modo che capiscano cosa sta succedendo.

Ora, questo pezzo di codice mi sta causando sicuramente dei problemi.

#include <stdio.h> 

int test(int x) 
{ 
     int y; 
     if(!x) 
       y=0; 
     y++; 
     printf("(y=%d, ", y); 
     return y; 
} 

int main(void) 
{ 
     int a, i; 

     for(i=0; i<5; i++) 
     { 
       a=test(i); 
       printf("a=%d), ", a); 
     } 
     printf("\n"); 
     return 0; 
} 

L'output è:

(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4), (y=5, a=5), 

Ma se io commento la riga:

 /* printf("(y=%d, ", y); */ 

L'uscita diventano:

a=1), a=32720), a=32721), a=32722), a=32723), 

compilo il codice utilizzando -Wall passare, ma nessun avviso gs sono correlati all'uso di variabili locali senza inizializzarle.

C'è qualche interruttore GCC per causare un avviso o almeno per mostrare esplicitamente un po 'di spazzatura? Ho provato le opzioni di ottimizzazione, e che ha aiutato come output codice divenne simili:

$ gcc test.c -o test -Wall -Os 
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), 
$ gcc test.c -o test -Wall -Ofast 
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), 
$ gcc test.c -o test -Wall -O0 
$ ./test 
(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4), (y=5, a=5), 
$ gcc test.c -o test -Wall -O1 
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), 
$ gcc test.c -o test -Wall -O2 
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), 
$ gcc test.c -o test -Wall -O3 
$ ./test 
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), 

Ma y = 1 in tutti i casi è tipo di trucco. Lo standard è cambiato così le variabili locali sono ora inizializzate con zeri?

+0

Non penso che C specifichi uno standard per l'intializzazione delle variabili. In effetti, penso che la memoria rimanga così com'è, quindi quando si aggiunge a y, il valore di y era lo stesso di prima, e quindi lo si incrementa. – Magn3s1um

+0

La coincidenza è che la seconda chiamata alla funzione sta utilizzando lo stesso codice di blocco della RAM. –

+1

Non penso che sia una coincidenza, dal momento che il compilatore collegherà y ad una certa regione di memoria che è relativa. Non cambia la relazione di y con il primo stack, quindi se lo stack precedente non aumenta di dimensioni, y sarà sempre nello stesso posto! – Magn3s1um

risposta

2

Un altro possibile approccio a questa idea potrebbe essere quello di chiamare un'altra funzione tra le chiamate alla funzione test. Se l'altra funzione utilizza lo spazio di stack, probabilmente finirà per cambiare i valori dello stack. Per esempio, forse aggiungere una funzione come questa:

int changeStack(int x) 
{ 
    int y2 = x + 100; 
    return y2; 
} 

E poi aggiungere una chiamata ad esso:

for(i=0; i<10; i++) 
    { 
      a=test(i); 
      printf("a=%d), ", a); 
      changeStack(i); 
    } 

E, naturalmente, dipende dai livelli di ottimizzazione, ma con la compilazione di default gcc (gcc test.c), ho ottenuto i seguenti risultati dopo averlo cambiato per farlo:

(y=1, a=1), (y=101, a=101), (y=102, a=102), (y=103, a=103), (y=104, a=104), (y=105, a=105), (y=106, a=106), (y=107, a=107), (y=108, a=108), (y=109, a=109), 
+0

Questo approccio potrebbe funzionare a causa della propensione di GCC a memorizzare tutto in memoria se non vengono richieste ottimizzazioni. Aggiungere una bandiera '-O' lo farà quasi certamente andare via, comunque. –

+1

@CarlNorum: Sì, è sicuramente una dimostrazione molto specifica per il compilatore e specifica per l'ottimizzazione. L'uso di qualsiasi ottimizzazione cambia effettivamente il risultato. Tricky business che cerca di dimostrare un comportamento indefinito. –

+0

Mark, hai capito. Penso che possa usare questo esempio in classe. Grazie! –

2

Il programma causa un comportamento non definito. Se si passa un valore diverso da zero a test(), il valore non viene mai inizializzato. printf incluso o meno, non puoi fare affidamento sui risultati.

Se si desidera essere avvisati, clang vi darà uno con -Wsometimes-uninitialized:

example.c:6:12: warning: variable 'y' is used uninitialized whenever 'if' 
     condition is false [-Wsometimes-uninitialized] 
     if(!x) 
      ^~ 
example.c:8:9: note: uninitialized use occurs here 
     y++; 
     ^
example.c:6:9: note: remove the 'if' if its condition is always true 
     if(!x) 
     ^~~~~~ 
example.c:5:14: note: initialize the variable 'y' to silence this warning 
     int y; 
      ^
       = 0 
1 warning generated. 

ho provato con un paio di versioni di GCC che ho a portata di mano, ma nessuno di loro avrebbe prodotto un avvertimento per me.

+0

Lo so.Voglio applicare questa conferenza, utilizzando GCC (in modo che gli studenti possano vedere da soli un codice di errore restituito dalla compilazione) –

+0

Ho appena modificato il ciclo di codice in 'for (i = 1; i <5; i ++)' e il risultato è stato: '(y = 1, a = 1), (y = 2, a = 2), (y = 3, a = 3), (y = 4, a = 4),'. Questo è terribile in una classe piena di jolly. :) –

+0

Ho provato con gcc switch ('-Wmaybe-uninitialized') ma senza preavviso. Grazie lo stesso. Bel consiglio. –

4

Questo è il problema con un comportamento indefinito: è "indefinito".

Quindi, qualsiasi insieme di risultati è interamente legato a una combinazione di compilatore/impostazioni/cosa c'è in memoria/interruzioni.

È possibile possibilità su alcune impostazioni che emettono ciò che si "aspetta", per dimostrare il problema - ma questa è solo fortuna.

Quello che hai scoperto è in realtà più importante: il numero di modalità di errore è più ampio di quanto tu possa immaginare (anche se fortunatamente nessuno ha ancora riformattato il tuo disco rigido) e quello il tipo più pernicioso e pericoloso di ' comportamento non definito ' è quello in cui il comportamento è effettivamente' come previsto 'per il 99,99% delle volte.

È lo 0,01% che si ottiene.