Perché questo loop produce "avvertimento: l'iterazione 3u invoca un comportamento indefinito" e genera più di 4 righe?


162

Compilando questo:

#include <iostream>

int main()
{
    for (int i = 0; i < 4; ++i)
        std::cout << i*1000000000 << std::endl;
}

e gccproduce il seguente avviso:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

Capisco che c'è un overflow di numeri interi con segno.

Quello che non riesco a capire è perché il ivalore è rotto da quell'operazione di overflow?

Ho letto le risposte a Perché l'overflow di numeri interi su x86 con GCC provoca un ciclo infinito? , ma non sono ancora chiaro sul perché ciò accada: ottengo che "indefinito" significa "tutto può succedere", ma qual è la causa di fondo di questo comportamento specifico ?

Online: http://ideone.com/dMrRKR

Compiler: gcc (4.8)


49
Overflow intero con segno => Comportamento indefinito => Demoni nasali. Ma devo ammettere che questo esempio è abbastanza carino.
dyp


1
Succede su GCC 4.8 con O2, e O3bandiera, ma non O0oO1
Alex

3
@dyp quando ho letto i demoni nasali ho fatto la "risata imgur" che consiste nell'espirare leggermente il naso quando vedi qualcosa di divertente. E poi ho capito ... Devo essere maledetto da un demone nasale!
corsiKa

4
Aggiungendo un segnalibro in modo da poterlo collegare la prossima volta che qualcuno replica "è tecnicamente UB ma dovrebbe fare qualcosa " :)
MM

Risposte:


107

Overflow intero con segno (in senso stretto, non esiste "overflow intero senza segno") significa comportamento indefinito . E questo significa che tutto può succedere e discutere del perché ciò avvenga secondo le regole del C ++ non ha senso.

Bozza C ++ 11 N3337: §5.4: 1

Se durante la valutazione di un'espressione, il risultato non è definito matematicamente o non è compreso nell'intervallo di valori rappresentabili per il suo tipo, il comportamento non è definito. [Nota: la maggior parte delle implementazioni esistenti di C ++ ignora i flussi di numeri interi. Il trattamento della divisione per zero, formando un resto usando un divisore zero, e tutte le eccezioni dei punti fluttuanti variano tra le macchine ed è di solito regolabile da una funzione di libreria. —Endola nota]

Il tuo codice compilato con g++ -O3avviso emette (anche senza -Wall)

a.cpp: In function 'int main()':
a.cpp:11:18: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^
a.cpp:9:2: note: containing loop
  for (int i = 0; i < 4; ++i)
  ^

L'unico modo per analizzare cosa sta facendo il programma è leggere il codice assembly generato.

Ecco l'elenco completo dell'assemblaggio:

    .file   "a.cpp"
    .section    .text$_ZNKSt5ctypeIcE8do_widenEc,"x"
    .linkonce discard
    .align 2
LCOLDB0:
LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  __ZNKSt5ctypeIcE8do_widenEc
    .def    __ZNKSt5ctypeIcE8do_widenEc;    .scl    2;  .type   32; .endef
__ZNKSt5ctypeIcE8do_widenEc:
LFB860:
    .cfi_startproc
    movzbl  4(%esp), %eax
    ret $4
    .cfi_endproc
LFE860:
LCOLDE0:
LHOTE0:
    .section    .text.unlikely,"x"
LCOLDB1:
    .text
LHOTB1:
    .p2align 4,,15
    .def    ___tcf_0;   .scl    3;  .type   32; .endef
___tcf_0:
LFB1091:
    .cfi_startproc
    movl    $__ZStL8__ioinit, %ecx
    jmp __ZNSt8ios_base4InitD1Ev
    .cfi_endproc
LFE1091:
    .section    .text.unlikely,"x"
LCOLDE1:
    .text
LHOTE1:
    .def    ___main;    .scl    2;  .type   32; .endef
    .section    .text.unlikely,"x"
LCOLDB2:
    .section    .text.startup,"x"
LHOTB2:
    .p2align 4,,15
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB1084:
    .cfi_startproc
    leal    4(%esp), %ecx
    .cfi_def_cfa 1, 0
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    .cfi_escape 0x10,0x5,0x2,0x75,0
    movl    %esp, %ebp
    pushl   %edi
    pushl   %esi
    pushl   %ebx
    pushl   %ecx
    .cfi_escape 0xf,0x3,0x75,0x70,0x6
    .cfi_escape 0x10,0x7,0x2,0x75,0x7c
    .cfi_escape 0x10,0x6,0x2,0x75,0x78
    .cfi_escape 0x10,0x3,0x2,0x75,0x74
    xorl    %edi, %edi
    subl    $24, %esp
    call    ___main
L4:
    movl    %edi, (%esp)
    movl    $__ZSt4cout, %ecx
    call    __ZNSolsEi
    movl    %eax, %esi
    movl    (%eax), %eax
    subl    $4, %esp
    movl    -12(%eax), %eax
    movl    124(%esi,%eax), %ebx
    testl   %ebx, %ebx
    je  L15
    cmpb    $0, 28(%ebx)
    je  L5
    movsbl  39(%ebx), %eax
L6:
    movl    %esi, %ecx
    movl    %eax, (%esp)
    addl    $1000000000, %edi
    call    __ZNSo3putEc
    subl    $4, %esp
    movl    %eax, %ecx
    call    __ZNSo5flushEv
    jmp L4
    .p2align 4,,10
L5:
    movl    %ebx, %ecx
    call    __ZNKSt5ctypeIcE13_M_widen_initEv
    movl    (%ebx), %eax
    movl    24(%eax), %edx
    movl    $10, %eax
    cmpl    $__ZNKSt5ctypeIcE8do_widenEc, %edx
    je  L6
    movl    $10, (%esp)
    movl    %ebx, %ecx
    call    *%edx
    movsbl  %al, %eax
    pushl   %edx
    jmp L6
L15:
    call    __ZSt16__throw_bad_castv
    .cfi_endproc
LFE1084:
    .section    .text.unlikely,"x"
LCOLDE2:
    .section    .text.startup,"x"
LHOTE2:
    .section    .text.unlikely,"x"
LCOLDB3:
    .section    .text.startup,"x"
LHOTB3:
    .p2align 4,,15
    .def    __GLOBAL__sub_I_main;   .scl    3;  .type   32; .endef
__GLOBAL__sub_I_main:
LFB1092:
    .cfi_startproc
    subl    $28, %esp
    .cfi_def_cfa_offset 32
    movl    $__ZStL8__ioinit, %ecx
    call    __ZNSt8ios_base4InitC1Ev
    movl    $___tcf_0, (%esp)
    call    _atexit
    addl    $28, %esp
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc
LFE1092:
    .section    .text.unlikely,"x"
LCOLDE3:
    .section    .text.startup,"x"
LHOTE3:
    .section    .ctors,"w"
    .align 4
    .long   __GLOBAL__sub_I_main
.lcomm __ZStL8__ioinit,1,1
    .ident  "GCC: (i686-posix-dwarf-rev1, Built by MinGW-W64 project) 4.9.0"
    .def    __ZNSt8ios_base4InitD1Ev;   .scl    2;  .type   32; .endef
    .def    __ZNSolsEi; .scl    2;  .type   32; .endef
    .def    __ZNSo3putEc;   .scl    2;  .type   32; .endef
    .def    __ZNSo5flushEv; .scl    2;  .type   32; .endef
    .def    __ZNKSt5ctypeIcE13_M_widen_initEv;  .scl    2;  .type   32; .endef
    .def    __ZSt16__throw_bad_castv;   .scl    2;  .type   32; .endef
    .def    __ZNSt8ios_base4InitC1Ev;   .scl    2;  .type   32; .endef
    .def    _atexit;    .scl    2;  .type   32; .endef

Riesco a malapena a leggere l'assemblaggio, ma riesco anche a vedere la addl $1000000000, %ediriga. Il codice risultante appare più simile

for(int i = 0; /* nothing, that is - infinite loop */; i += 1000000000)
    std::cout << i << std::endl;

Questo commento di @TC:

Ho il sospetto che sia qualcosa del tipo: (1) perché ogni iterazione con iqualsiasi valore maggiore di 2 ha un comportamento indefinito -> (2) possiamo supporre che i <= 2ai fini dell'ottimizzazione -> (3) la condizione del ciclo sia sempre vera -> (4 ) è ottimizzato in un ciclo infinito.

mi è venuta l'idea di confrontare il codice assembly del codice OP con il codice assembly del codice seguente, senza un comportamento indefinito.

#include <iostream>

int main()
{
    // changed the termination condition
    for (int i = 0; i < 3; ++i)
        std::cout << i*1000000000 << std::endl;
}

E, in effetti, il codice corretto ha una condizione di terminazione.

    ; ...snip...
L6:
    mov ecx, edi
    mov DWORD PTR [esp], eax
    add esi, 1000000000
    call    __ZNSo3putEc
    sub esp, 4
    mov ecx, eax
    call    __ZNSo5flushEv
    cmp esi, -1294967296 // here it is
    jne L7
    lea esp, [ebp-16]
    xor eax, eax
    pop ecx
    ; ...snip...

OMG, non è del tutto ovvio! Non è giusto! Chiedo una prova col fuoco!

Affrontalo, hai scritto il codice di errore e dovresti sentirti male. Sopportare le conseguenze.

... o, in alternativa, fare un uso corretto di una migliore diagnostica e di migliori strumenti di debug - ecco a cosa servono:

  • abilita tutti gli avvisi

    • -Wallè l'opzione gcc che abilita tutti gli avvisi utili senza falsi positivi. Questo è un minimo indispensabile che dovresti sempre usare.
    • gcc ha molte altre opzioni di avvertimento , tuttavia non sono abilitate -Wallperché potrebbero avvisare di falsi positivi
    • Visual C ++ purtroppo è in ritardo con la possibilità di fornire avvisi utili. Almeno l'IDE ne abilita alcuni per impostazione predefinita.
  • utilizzare i flag di debug per il debug

    • per l'overflow di numeri interi -ftrapvintercetta il programma sull'overflow,
    • Compilatore Clang è eccellente per questo: -fcatch-undefined-behaviorcattura un sacco di esempi di comportamento non definito (nota: "a lot of" != "all of them")

Ho un pasticcio di spaghetti di un programma non scritto da me che deve essere spedito domani! AIUTO !!!!!! 111oneone

Usa gcc -fwrapv

Questa opzione indica al compilatore di supporre che l'overflow aritmetico firmato di addizione, sottrazione e moltiplicazione si avvolga usando la rappresentazione a doppio complemento.

1 - questa regola non si applica al "overflow di numeri interi senza segno", come afferma il §3.9.1.4

Gli interi senza segno, dichiarati senza segno, devono obbedire alle leggi dell'aritmetica modulo 2 n dove n è il numero di bit nella rappresentazione del valore di quella particolare dimensione di intero.

e per esempio il risultato di UINT_MAX + 1è matematicamente definito - dalle regole dell'aritmetica modulo 2 n


7
Ancora non capisco davvero cosa stia succedendo qui ... Perché ne è icolpito? In generale, un comportamento indefinito non sta avendo questo tipo di strani effetti collaterali, dopo tutto, i*100000000dovrebbe essere un valore
vsoftco,

26
Ho il sospetto che sia qualcosa del tipo: (1) perché ogni iterazione con iqualsiasi valore maggiore di 2 ha un comportamento indefinito -> (2) possiamo supporre che i <= 2ai fini dell'ottimizzazione -> (3) la condizione del ciclo sia sempre vera -> (4 ) è ottimizzato in un ciclo infinito.
TC,

28
@vsoftco: Quello che sta succedendo è un caso di riduzione della forza , in particolare dell'eliminazione della variabile di induzione . Il compilatore elimina la moltiplicazione emettendo codice che invece aumenta idi 1e9 ogni iterazione (e modificando le condizioni del loop di conseguenza). Questa è un'ottimizzazione perfettamente valida sotto la regola "come se" in quanto questo programma non ha potuto osservare la differenza se si comportasse bene. Ahimè, non lo è, e l'ottimizzazione "perde".
JohannesD,

8
@JohannesD ha inchiodato il motivo per cui si rompe. Tuttavia, questa è una cattiva ottimizzazione poiché la condizione di terminazione del loop non comporta un overflow. L'uso della riduzione della forza andava bene - non so che cosa farebbe il moltiplicatore nel processore (4 * 100000000) che sarebbe diverso con (100000000 + 100000000 + 100000000 + 100000000) e ricadendo su "non è definito - chissà "è ragionevole. Ma sostituendo quello che dovrebbe essere un ciclo ben educato, che viene eseguito 4 volte e produce risultati indefiniti, con qualcosa che viene eseguito più di 4 volte "perché non è definito!" è idiozia.
Julie ad Austin,

14
@JulieinAustin Anche se può essere idiota per te, è perfettamente legale. Sul lato positivo, il compilatore ti avverte al riguardo.
Milleniumbug

68

Risposta breve, in gccparticolare ha documentato questo problema, possiamo vedere che nelle note di rilascio di gcc 4.8 che dice ( enfatizzare il mio andare avanti ):

GCC ora utilizza un'analisi più aggressiva per ricavare un limite superiore per il numero di iterazioni di loop utilizzando i vincoli imposti dagli standard linguistici . Ciò potrebbe impedire ai programmi non conformi di non funzionare più come previsto, ad esempio SPEC CPU 2006 464.h264ref e 416.gamess. Una nuova opzione, -fno-aggressive-loop-ottimizzazioni, è stata aggiunta per disabilitare questa analisi aggressiva. In alcuni loop che hanno conosciuto un numero costante di iterazioni, ma è noto che si verifica un comportamento indefinito nel loop prima di raggiungere o durante l'ultima iterazione, GCC avviserà del comportamento indefinito nel loop invece di derivare il limite superiore inferiore del numero di iterazioni per il ciclo. L'avviso può essere disabilitato con ottimizzazioni -Wno-aggressive-loop.

e in effetti se usiamo -fno-aggressive-loop-optimizationsil comportamento del loop infinito dovrebbe cessare e lo fa in tutti i casi che ho testato.

La lunga risposta inizia con la consapevolezza che l' overflow di numeri interi con segno è un comportamento indefinito guardando la bozza C ++ standard sezione 5 Espressioni paragrafo 4 che dice:

Se durante la valutazione di un'espressione, il risultato non è definito matematicamente o non è compreso nell'intervallo di valori rappresentabili per il suo tipo, il comportamento non è definito . [Nota: la maggior parte delle implementazioni esistenti di C ++ ignora gli overflow di numeri interi. Il trattamento della divisione per zero, formando un resto usando un divisore zero, e tutte le eccezioni in virgola mobile variano tra le macchine, ed è di solito regolabile da una funzione di libreria. - nota

Sappiamo che lo standard dice che il comportamento indefinito è imprevedibile dalla nota fornita con la definizione che dice:

[Nota: un comportamento indefinito può essere previsto quando questo standard internazionale omette qualsiasi definizione esplicita di comportamento o quando un programma utilizza un costrutto errato o dati errati. Il comportamento non definito consentito va dall'ignorare completamente la situazione con risultati imprevedibili , al comportamento durante la traduzione o l'esecuzione del programma in un modo documentato caratteristico dell'ambiente (con o senza l'emissione di un messaggio diagnostico), alla conclusione di una traduzione o esecuzione (con l'emissione di un messaggio diagnostico). Molti costrutti di programma errati non generano comportamenti indefiniti; devono essere diagnosticati. —Endola nota]

Ma cosa può fare l' gccottimizzatore nel mondo per trasformarlo in un ciclo infinito? Sembra del tutto strano. Ma per fortuna gccci dà un indizio per capirlo nell'avvertimento:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

L'indizio è il Waggressive-loop-optimizations, cosa significa? Fortunatamente per noi questa non è la prima volta che questa ottimizzazione ha infranto il codice in questo modo e siamo fortunati perché John Regehr ha documentato un caso nell'articolo GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks che mostra il seguente codice:

int d[16];

int SATD (void)
{
  int satd = 0, dd, k;
  for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
  }
  return satd;
}

l'articolo dice:

Il comportamento indefinito sta accedendo a d [16] poco prima di uscire dal ciclo. In C99 è legale creare un puntatore a un elemento una posizione oltre la fine dell'array, ma quel puntatore non deve essere sottoposto a dereferenziazione.

e più tardi dice:

Nel dettaglio, ecco cosa sta succedendo. Il compilatore CA, dopo aver visto d [++ k], può assumere che il valore incrementato di k rientri nei limiti dell'array, poiché si verifica un comportamento altrimenti non definito. Per il codice qui, GCC può dedurre che k è compreso nell'intervallo 0..15. Un po 'più tardi, quando GCC vede k <16, si dice: "Ah, quell'espressione è sempre vera, quindi abbiamo un ciclo infinito". La situazione qui, in cui il compilatore utilizza il presupposto di una buona definizione per inferire un fatto utile del flusso di dati,

Quindi ciò che il compilatore deve fare in alcuni casi sta assumendo poiché l'overflow di numeri interi con segno è un comportamento indefinito, quindi ideve essere sempre minore di 4e quindi abbiamo un ciclo infinito.

Spiega che è molto simile alla famigerata rimozione del controllo del puntatore null del kernel Linux in cui nel vedere questo codice:

struct foo *s = ...;
int x = s->f;
if (!s) return ERROR;

gccdedotto che poiché è sstato rinviato s->f;e poiché la dereferenziazione di un puntatore null è un comportamento indefinito, snon deve essere nullo e quindi ottimizza il if (!s)controllo sulla riga successiva.

La lezione qui è che i moderni ottimizzatori sono molto aggressivi nello sfruttare comportamenti indefiniti e molto probabilmente diventeranno solo più aggressivi. Chiaramente con solo alcuni esempi possiamo vedere che l'ottimizzatore fa cose che sembrano completamente irragionevoli per un programmatore, ma a posteriori dal punto di vista degli ottimizzatori ha un senso.


7
Capisco che questo è ciò che sta facendo lo scrittore di compilatori (scrivevo compilatori e anche uno o due ottimizzatori), ma ci sono comportamenti che sono "utili" anche se sono "indefiniti" e questa marcia verso un'ottimizzazione sempre più aggressiva è solo follia. Il costrutto che citi sopra è sbagliato, ma l'ottimizzazione del controllo degli errori è ostile all'utente.
Julie ad Austin,

1
@JulieinAustin Sono d'accordo che questo è un comportamento piuttosto sorprendente, dicendo che gli sviluppatori devono evitare comportamenti indefiniti è davvero solo metà del problema. Chiaramente anche il compilatore deve fornire un feedback migliore allo sviluppatore. In questo caso viene prodotto un avviso sebbene non sia abbastanza informativo.
Shafik Yaghmour,

3
Penso che sia una buona cosa, voglio un codice migliore e più veloce. UB non dovrebbe mai essere usato.
paulm,

1
@paulm dal punto di vista morale UB è chiaramente negativo, ma è difficile discutere di fornire strumenti migliori e migliori come l' analizzatore statico di clang per aiutare gli sviluppatori a rilevare UB e altri problemi prima che incidano sulle applicazioni di produzione.
Shafik Yaghmour,

1
@ShafikYaghmour Inoltre, se il tuo sviluppatore ignora gli avvisi, quali sono le possibilità che prestino attenzione al clang output? Questo problema può essere facilmente risolto da una politica aggressiva "nessun avvertimento ingiustificato". Clang consigliabile ma non richiesto.
deworde,

24

tl; dr Il codice genera un test che integer + integer positivo == intero negativo . Di solito l'ottimizzatore non lo ottimizza, ma nel caso specifico dell'utilizzo std::endlsuccessivo, il compilatore ottimizza questo test. Non ho ancora capito cosa c'è di speciale endl.


Dal codice assembly a -O1 e livelli superiori, è chiaro che gcc trasforma il loop in:

i = 0;
do {
    cout << i << endl;
    i += NUMBER;
} 
while (i != NUMBER * 4)

Il valore più grande che funziona correttamente è 715827882, ovvero floor ( INT_MAX/3). Lo snippet di assembly in -O1è:

L4:
movsbl  %al, %eax
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
addl    $715827882, %esi
cmpl    $-1431655768, %esi
jne L6
    // fallthrough to "return" code

Nota, -1431655768è 4 * 715827882nel complemento a 2.

Colpire -O2ottimizza questo a quanto segue:

L4:
movsbl  %al, %eax
addl    $715827882, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655768, %esi
jne L6
leal    -8(%ebp), %esp
jne L6 
   // fallthrough to "return" code

Quindi l'ottimizzazione che è stata fatta è semplicemente che è addlstato spostato più in alto.

Se 715827883invece ricompiliamo invece la versione -O1 è identica a parte il numero modificato e il valore del test. Tuttavia, -O2 apporta quindi una modifica:

L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2

Dove c'era cmpl $-1431655764, %esia -O1, quella linea è stato rimosso -O2. L'ottimizzatore deve aver deciso che l'aggiunta 715827883a %esinon potrà mai eguagliare -1431655764.

Questo è abbastanza sconcertante. Aggiungendo che per INT_MIN+1 non generare il risultato atteso, cosicché l'ottimizzatore deve aver deciso che %esinon può mai essere INT_MIN+1e non sono sicuro perché sarebbe decidere che.

Nell'esempio funzionante sembra ugualmente valido concludere che l'aggiunta 715827882a un numero non può eguagliare INT_MIN + 715827882 - 2! (questo è possibile solo se si verifica effettivamente un avvolgimento), ma non ottimizza la linea in quell'esempio.


Il codice che stavo usando è:

#include <iostream>
#include <cstdio>

int main()
{
    for (int i = 0; i < 4; ++i)
    {
        //volatile int j = i*715827883;
        volatile int j = i*715827882;
        printf("%d\n", j);

        std::endl(std::cout);
    }
}

Se std::endl(std::cout)viene rimosso, l'ottimizzazione non si verifica più. Infatti, sostituirlo con std::cout.put('\n'); std::flush(std::cout);provoca anche l'ottimizzazione, anche se std::endlè inline.

L'allineamento di std::endlsembra influenzare la parte precedente della struttura del ciclo (che non capisco bene cosa stia facendo, ma lo posterò qui nel caso qualcun altro lo faccia):

Con codice originale e -O2:

L2:
movl    %esi, 28(%esp)
movl    28(%esp), %eax
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    __ZSt4cout, %eax
movl    -12(%eax), %eax
movl    __ZSt4cout+124(%eax), %ebx
testl   %ebx, %ebx
je  L10
cmpb    $0, 28(%ebx)
je  L3
movzbl  39(%ebx), %eax
L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2                  // no test

Con inlining mymanual di std::endl, -O2:

L3:
movl    %ebx, 28(%esp)
movl    28(%esp), %eax
addl    $715827883, %ebx
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    $10, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    $__ZSt4cout, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655764, %ebx
jne L3
xorl    %eax, %eax

Una differenza tra questi due è quella %esiusata nell'originale e %ebxnella seconda versione; c'è qualche differenza nella semantica definita tra %esie %ebxin generale? (Non so molto sull'assemblaggio x86).


Sarebbe bello scoprire esattamente quale fosse la logica dell'ottimizzatore, in questa fase non mi è chiaro perché in alcuni casi il test sia stato ottimizzato e altri no
MM

8

Un altro esempio di questo errore segnalato in gcc è quando hai un ciclo che viene eseguito per un numero costante di iterazioni, ma stai usando la variabile contatore come indice in un array che ha meno di quel numero di elementi, come:

int a[50], x;

for( i=0; i < 1000; i++) x = a[i];

Il compilatore può determinare che questo ciclo tenterà di accedere alla memoria all'esterno dell'array "a". Il compilatore si lamenta di questo con questo messaggio piuttosto criptico:

iteration xxu invoca un comportamento indefinito [-Werror = aggressive-loop-optimizations]


Ancora più criptico è che il messaggio viene emesso solo quando l'ottimizzazione è attivata. Il messaggio M $ VB "Matrice fuori limite" è per i manichini?
Ravi Ganesh,

6

Quello che non riesco a ottenere è perché apprezzo il fatto che sia rotto da quell'operazione di overflow?

Sembra che l'overflow dei numeri interi si verifichi nella quarta iterazione (per i = 3). signedl'overflow dei numeri interi richiama comportamenti indefiniti . In questo caso non è possibile prevedere nulla. Il ciclo può iterare solo 4volte o può andare all'infinito o qualsiasi altra cosa!
Il risultato può variare dal compilatore al compilatore o anche per versioni diverse dello stesso compilatore.

C11: 1.3.24 comportamento indefinito:

comportamento per il quale questo standard internazionale non impone requisiti
[Nota: un comportamento indefinito può essere previsto quando questo standard internazionale omette qualsiasi definizione esplicita di comportamento o quando un programma utilizza un costrutto errato o dati errati. Il comportamento non definito consentito va dall'ignorare completamente la situazione con risultati imprevedibili, al comportamento durante la traduzione o l'esecuzione del programma in un modo documentato caratteristico dell'ambiente (con o senza l'emissione di un messaggio diagnostico), alla conclusione di una traduzione o esecuzione (con l'emissione di un messaggio diagnostico) . Molti costrutti di programma errati non generano comportamenti indefiniti; devono essere diagnosticati. —Endola nota]


@bits_international; Sì.
Hawcks

4
Hai ragione, è giusto spiegare perché ho effettuato il downgrade. Le informazioni contenute in questa risposta sono corrette, ma non sono educative e ignorano completamente l'elefante nella stanza: la rottura apparentemente avviene in un posto diverso (condizione di arresto) rispetto all'operazione che causa il trabocco. La meccanica di come le cose sono rotte in questo caso specifico non sono spiegate, anche se questo è il nocciolo di questa domanda. È una tipica situazione dell'insegnante in cui la risposta dell'insegnante non solo non affronta il nocciolo del problema, ma scoraggia ulteriori domande. Sembra quasi ...
Szabolcs,

5
"Vedo che si tratta di un comportamento indefinito, e da questo momento in poi non mi interessa come o perché si rompe. Lo standard consente di interrompere. Nessuna ulteriore domanda." Potresti non averlo inteso in quel modo, ma sembra così. Spero di vedere meno di questo atteggiamento (sfortunatamente comune) su SO. Questo non è praticamente utile. Se si riceve l'input dell'utente, non è ragionevole verificare la presenza di overflow dopo ogni singola operazione di intero con segno , anche se lo standard dice che qualsiasi altra parte del programma potrebbe esplodere a causa di ciò. Capire come si rompe fa help problemi evitare come questo in pratica.
Szabolcs,

2
@Szabolcs: potrebbe essere meglio pensare a C come a due lingue, una delle quali è stata progettata per consentire ai compilatori semplici di ottenere un codice eseguibile ragionevolmente efficiente con l'aiuto di programmatori che sfruttano costrutti che sarebbero affidabili sulle loro piattaforme target previste ma non altri, e di conseguenza sono stati ignorati dal comitato per le norme e una seconda lingua che esclude tutti quei costrutti per i quali la norma non impone il supporto, allo scopo di consentire ai compilatori di applicare ulteriori ottimizzazioni che possono o meno superare quelle che i programmatori devono abbandonare.
supercat

1
@Szabolcs " Se ricevi l'input dell'utente, non è ragionevole controllare lo sfioro dopo ogni singola operazione di numero intero con segno " - correggi perché a quel punto è troppo tardi. È necessario verificare l'overflow prima di ogni singola operazione intera con segno .
melpomene,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.