2014-10-10 2 views
5

So che quando si eseguono determinate operazioni in un programma C, i risultati non sono definiti. Tuttavia, il compilatore non dovrebbe generare codice (macchina) non valido, giusto? Sarebbe ragionevole se il codice facesse la cosa sbagliata, o se il codice generasse un segfault o qualcosa del genere ...Codice C con risultati non definiti, il compilatore genera codice non valido (con -O3)

Questo dovrebbe accadere secondo le specifiche del compilatore, o è un bug nel compilatore?

Ecco il programma (semplice) sto usando:

int main() { 
    char *ptr = 0; 
    *(ptr) = 0; 
} 

Sto compilando con -O3. Ciò non dovrebbe tuttavia generare istruzioni hardware non valide, giusto? Con -O0, ottengo un segfault quando eseguo il codice. Sembra molto più sano.

Edit: E 'la generazione di un'istruzione ud2 ...

+7

UB significa che tutte le scommesse sono state annullate, il buon senso è stato scartato molto tempo fa, nessun motivo per lamentarsi! – Deduplicator

+1

possibile duplicato di [comportamento non definito, non specificato e definito dall'implementazione] (http://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior) – Deduplicator

+0

Non sapevo che GCC ha generato un ud2 con comportamento non definito, ma CLang lo fa: http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html –

risposta

7

L'istruzione ud2 è una "valida istruzione"e' l'acronimo di istruzione non definita e genera un codice operativo non valido eccezione clang e apparentemente gcc ci riesce genera questo codice quando un programma richiama un comportamento non definito.

Dal link clang sopra il razionale è spiegato come segue:

Memorizza NULL e chiamate attraverso puntatori nulli vengono trasformate in una chiamata __builtin_trap() (che si trasforma in un'istruzione di intrappolamento come "UD2" su x86). Questi si verificano sempre in codice ottimizzato (come il risultato di altre trasformazioni come Inlining e Constant di propagazione) e abbiamo usato solo per cancellare i blocchi che li contenevano perché erano "ovviamente irraggiungibili".

Mentre (dal punto di vista legale linguaggio pedante) ciò sia strettamente vero, abbiamo rapidamente imparato che la gente fa di tanto in tanto dereferenziare nulli puntatori, e avendo l'esecuzione di codice appena cadere in cima alla funzione successiva rende molto difficile capire il problema. Da l'angolo di prestazione, l'aspetto più importante dell'esposizione di questi è per lo squash del codice downstream. Per questo motivo, clang trasforma questi in una trappola di runtime : se uno di questi viene effettivamente raggiunto dinamicamente, il programma si arresta immediatamente e può essere sottoposto a debug. Lo svantaggio di fare è che il codice è leggermente gonfio avendo queste operazioni e con le condizioni che controllano i loro predicati.

alla fine della giornata, una volta che si sta invocando un comportamento indefinito, il comportamento del programma è imprevedibile. La filosofia qui è che probabilmente è meglio schiantarsi duramente e dare allo sviluppatore un'indicazione che qualcosa è seriamente sbagliato e consentire loro di eseguire il debug dal punto giusto piuttosto che produrre un programma che sembra funzionare ma in realtà è rotto.

Come osserva Ruslan, è "valido" nel senso che ha garantito di generare un'eccezione opcode non valida rispetto ad altre sequenze inutilizzate che potrebbero in futuro diventare valide.

+1

Bene, 'ud2' è valido come' dw 0xffff'. L'unica cosa che lo rende "valido" è che è garantito che sia sempre invalido, mentre altre sequenze di byte non valide possono essere pensate come riservate e potrebbero essere valide nelle future implementazioni della CPU. – Ruslan

+0

@Ruslan è un buon punto. –