2011-01-01 12 views
10

Uno dei miei amici ha inviato questo codice per me, dicendo che non funziona come previsto:uscita imprevisto dal programma bubblesort con MSVC vs TCC

#include<stdio.h> 

void main() 
{ 
    int a [10] ={23, 100, 20, 30, 25, 45, 40, 55, 43, 42}; 
    int sizeOfInput = sizeof(a)/sizeof(int); 
    int b, outer, inner, c; 
    printf("Size is : %d \n", sizeOfInput); 

    printf("Values before bubble sort are : \n"); 
    for (b = 0; b &lt; sizeOfInput; b++) 
     printf("%d\n", a[b]); 

    printf("End of values before bubble sort... \n"); 

    for (outer = sizeOfInput; outer > 0; outer--) 
    { 
     for ( inner = 0 ; inner < outer ; inner++) 
     { 
     printf ("Comparing positions: %d and %d\n",inner,inner+1); 
     if (a[inner] > a[inner + 1]) 
     { 
       int tmp = a[inner]; 
       a[inner] = a [inner+1]; 
      a[inner+1] = tmp; 
      } 
     } 
     printf ("Bubble sort total array size after inner loop is %d :\n",sizeOfInput); 
     printf ("Bubble sort sizeOfInput after inner loop is %d :\n",sizeOfInput); 
    } 
    printf ("Bubble sort total array size at the end is %d :\n",sizeOfInput); 
    for (c = 0 ; c < sizeOfInput; c++) 
     printf("Element: %d\n", a[c]); 
} 

Sto usando Micosoft comandi di Visual Studio Strumento Line per compilarlo su una macchina Windows XP.

Il mio amico ottiene l'output corretto su una macchina di dinosauri (il codice viene compilato utilizzando TCC lì).
Il mio output è inaspettato. L'array cresce misteriosamente di dimensioni, in mezzo.

Se si modifica il codice in modo che la variabile sizeOfInput sia stata modificata in sizeOfInputt, fornisce i risultati previsti!

Una ricerca eseguita a Microsoft Visual C++ Developer Center non fornisce risultati per "sizeOfInput".

Non sono un esperto di C/C++ e sono curioso di scoprire perché questo accade - qualche esperto di C/C++ che può "fare un po 'di luce" su questo?
Nota non correlata: ho seriamente pensato di riscrivere l'intero codice per utilizzare quicksort o unire l'ordinamento prima di postarlo qui. Ma, dopo tutto, non è Stooge sort ...

Edit: so che il codice non è corretto (si legge oltre l'ultimo elemento), ma sono curioso perché il nome della variabile fa la differenza.

+0

Grazie per la modifica, ma appena notato che incasinato le (<>). Tag > e <. – crnlx

risposta

27

Come interjay's answer mentioned, una volta entrati in un comportamento indefinito tutte le scommesse sono disattivate. Tuttavia, quando hai detto che la semplice modifica della denominazione della variabile ha cambiato il comportamento del programma, mi sono incuriosito da cosa stava accadendo (indefinito o no).

In primo luogo, non credevo che la rinomina della variabile avrebbe cambiato l'output del compilatore (a parità di tutte le altre cose), ma sicuramente - quando l'ho provato, sono rimasto sorpreso nel vedere esattamente quello che hai descritto.

Quindi il compilatore ha eseguito il dump dell'assembly per il codice che stava generando per ogni versione del file di origine e ha eseguito un confronto.Ecco cosa ho trovato nella descrizione compilatori di come è stato gettando le variabili locali:

***** test.sizeOfInput.cod 
    _c$ = -56     ; size = 4 
    _b$ = -52     ; size = 4 
    _inner$ = -48    ; size = 4 
    _a$ = -44     ; size = 40 
>>> _sizeOfInput$ = -4   ; size = 4 
    _main PROC 
    ***** test.sizeOfInputt.cod 
    _c$ = -56     ; size = 4 
>>> _sizeOfInputt$ = -52   ; size = 4 
    _b$ = -48     ; size = 4 
    _inner$ = -44    ; size = 4 
    _a$ = -40     ; size = 40 
    _main PROC 
    ***** 

cosa che noterete è che quando la variabile si chiama sizeOfInput, ha compilatore lo colloca ad un indirizzo superiore a matrice a (appena oltre la fine della matrice) e quando la variabile è denominata sizeOfInputt, la colloca a un indirizzo inferiore rispetto all'array a invece che appena oltre la fine dell'array. Ciò significa che nella build che ha la variabile denominata sizeOfInput, il comportamento non definito che si verifica quando si modifica a[10] sta cambiando il valore di sizeOfInput. Nella build che utilizza il nome sizeOfInputt, poiché quella variabile non si trova alla fine dell'array, la scrittura su a[10] elimina qualcos'altro.

Sul motivo per cui il compilatore distribuirebbe le variabili in modo diverso quando il nome di uno cambia in modo apparentemente insignificante - non ne ho idea.

Ma questo è un buon esempio del perché non si debba contare sul layout delle variabili locali (o praticamente su tutte le variabili, sebbene sia possibile contare sull'ordine di layout degli elementi struct), e perché quando si tratta di undefined comportamento, "funziona sulla mia macchina" non lo taglia come prova che qualcosa funziona.

+0

Michael, questo aiuta! Questo è quello che stavo cercando. Accettare la tua risposta Grazie per il tempo dedicato alla ricerca di questo. – crnlx

+8

Probabilmente tutte le variabili sono messe in una qualche forma di mappa hash e gli hash di 'sizeOfInput' e' sizeOfInputt' sono semplicemente diversi. –

6

Il codice legge oltre la fine dell'array. Il valore massimo di outer è 10 e il valore massimo di inner è 9, quindi a[inner+1] leggerà a[10]. Questo ti darà undefined behavior, il che spiega perché diversi compilatori danno risultati diversi.

Per quanto riguarda il nome della variabile che fa la differenza: probabilmente no. È possibile che se si esegue lo stesso codice due volte (utilizzando lo stesso nome variabile), si otterranno risultati diversi. Fondamentalmente, quando si invoca un comportamento non definito, non si può essere sicuri di nulla che il programma farà, quindi è meglio non cercare di trovare un significato in cose come nomi di variabili.

C'è anche una possibilità che il nome della variabile faccia la differenza. Ciò dipende dall'implementazione del compilatore: ad esempio, l'utilizzo di un nome di variabile diverso potrebbe far sì che il compilatore organizzi la memoria in qualche modo in modo diverso, il che potrebbe causare una diversa interpretazione del programma. Penso che la maggior parte dei compilatori produrrebbe lo stesso codice se cambierai i nomi delle variabili, quindi probabilmente era solo una questione di fortuna.

+0

interjay: Grazie, ho aggiornato il mio post. So che il codice legge un altro elemento: è evidente dall'output. Sono perplesso sul nome della variabile che fa una tale differenza. – crnlx

+0

@interjay: Ho visto il tuo aggiornamento: no, se nomino la variabile come qualcos'altro, si compila e mi dà la risposta giusta la prima volta stessa. Inoltre, la ricompilazione con "sizeOfInput" non ha fatto alcuna differenza. – crnlx

+0

@Sujith: Anche se è vero, è quasi impossibile dire con certezza perché succede senza esaminare il codice sorgente del compilatore. Ho fatto un'altra modifica per dare un esempio. – interjay

1

Michael Burr's reply espone un comportamento interessante del compilatore. Ma dubito ancora che il nome della variabile locale possa influenzare il suo layout nello stack per il record attivo della funzione.

Sto usando VC++ 2013 con la riga di comando sopra (cl.exe/EHsc progam.cpp) e non posso ripeterlo - cambiare il nome della variabile non cambia il comportamento del programma, invece, mostra il crash casuale in modo stabile (alcuni esecuzioni restituiscono i risultati vanno bene e alcuni vanno in crash).

La ragione per l'incidente casuale sopra è __security_cookie è memorizzato direttamente sopra (indirizzo più grande) della matrice a, e come è definito come array intero bruciacchiato, il risultato dipenderà dal bit di segno (mis-interpretati) del valore di __security_cookie. Se è un numero intero positivo maggiore di 100, è ancora il valore più grande nell'array a, quindi l'ordinamento non lo cambierà in altre posizioni, quindi il controllo (__security_check_cookie) alla fine della funzione andrà bene. Se è inferiore a 100 o numero intero negativo, verrà passato all'elemento inferiore nell'array dopo l'ordinamento in modo che __security_check_cookie segnalazioni non riuscite. Ciò significa che il comportamento del programma dipende dal valore generato casualmente per __security_cookie.

Ho modificato il programma di test originale in basso per facilitare il test. Ho anche esteso l'output per includere l'elemento off-by-one (arrayLen + 1) e potremmo prevedere il comportamento in base al valore originale nell'elemento dopo l'array definito a.

#include<stdio.h> 
#define arrayLen sizeOfInputt 

void main() 
{ 
    int a [10] ={23, 100, 20, 30, 25, 45, 40, 55, 43, 42}; 
    int arrayLen = sizeof(a)/sizeof(int); 
    int b, outer, inner, c; 
    printf("Size is : %d \n", arrayLen); 

    printf("Values before bubble sort are : \n"); 
    for (b = 0; b < arrayLen + 1; b++) 
     printf("%d\n", a[b]); 

    printf("End of values before bubble sort... \n"); 

    for (outer = arrayLen; outer > 0; outer--) 
    { 
     for ( inner = 0 ; inner < outer ; inner++) 
     { 
     printf ("Comparing positions: %d and %d\n",inner,inner+1); 
     if (a[inner] > a[inner + 1]) 
     { 
       int tmp = a[inner]; 
       a[inner] = a [inner+1]; 
      a[inner+1] = tmp; 
      } 
     } 
     printf ("Bubble sort total array size after inner loop is %d :\n",arrayLen); 
     printf ("Bubble sort arrayLen after inner loop is %d :\n",arrayLen); 
    } 
    printf ("Bubble sort total array size at the end is %d :\n",arrayLen); 
    for (c = 0 ; c < arrayLen; c++) 
     printf("Element: %d\n", a[c]); 
}