Prima di tutto: so che molti bug di ottimizzazione sono dovuti a errori di programmazione o dipendono da fatti che possono cambiare a seconda delle impostazioni di ottimizzazione (valori in virgola mobile, problemi di multithreading, ...).Errore di programmazione o errore di programmazione?
Tuttavia, ho riscontrato un errore molto difficile da trovare e sono un po 'insicuro se esiste un modo per evitare che questo tipo di errore si verifichi senza disattivare l'ottimizzazione. Mi sto perdendo qualcosa? Questo potrebbe davvero essere un bug di ottimizzazione? Ecco un esempio semplificato:
struct Data {
int a;
int b;
double c;
};
struct Test {
void optimizeMe();
Data m_data;
};
void Test::optimizeMe() {
Data * pData; // Note that this pointer is not initialized!
bool first = true;
for (int i = 0; i < 3; ++i) {
if (first) {
first = false;
pData = &m_data;
pData->a = i * 10;
pData->b = i * pData->a;
pData->c = pData->b/2;
} else {
pData->a = ++i;
} // end if
} // end for
};
int main(int argc, char *argv[]) {
Test test;
test.optimizeMe();
return 0;
}
Il vero programma ovviamente ha molto altro da fare. Ma tutto si riduce al fatto che invece di accedere direttamente a m_data, viene utilizzato un puntatore (precedentemente unitializzato). Non appena aggiungo sufficienti dichiarazioni alla if (first)
-parte, l'ottimizzatore sembra cambiare il codice per qualcosa in queste righe:
if (first) {
first = false;
// pData-assignment has been removed!
m_data.a = i * 10;
m_data.b = i * m_data.a;
m_data.c = m_data.b/m_data.a;
} else {
pData->a = ++i; // This will crash - pData is not set yet.
} // end if
Come si può vedere, si sostituisce il superfluo puntatore dereference con una scrittura diretta al membro struct. Tuttavia non lo fa nell'else
-branch. Rimuove anche l'assegnazione pData
. Dato che il puntatore è ora ancora unitializzato, il programma si bloccherà nell'else
-branch.
naturalmente ci sono diverse cose che potrebbero essere migliorati qui, così si potrebbe dare la colpa al programmatore:
- dimenticare il puntatore e fare ciò che l'ottimizzatore fa - usare
m_data
direttamente. - Inizializza pData su nullptr - in questo modo l'ottimizzatore sa che il
else
-branch avrà esito negativo se il puntatore non viene mai assegnato. Almeno sembra risolvere il problema nel mio ambiente di test. - Sposta l'assegnazione del puntatore davanti al ciclo (inizializzando in modo efficace
pData
con&m_data
, che potrebbe anche essere un riferimento anziché un puntatore (per buona misura). Ciò ha senso in quanto pData è necessario in tutti i casi quindi non c'è motivo per fare questo all'interno del ciclo
il codice è ovviamente puzzolente, a dir poco, e non sto cercando di "colpa" l'ottimizzatore per fare questo, ma mi sto chiedendo:.. cosa sono Il programma potrebbe essere brutto, ma è un codice valido ...
Devo aggiungere che sto usando VS2012 con C + +/CLI e v110_xp-Toolset. L'ottimizzazione è impostata su/O2. Tieni inoltre presente che se vuoi davvero riprodurre il problema (non è proprio questo il punto di questa domanda), devi giocare con la complessità del programma. Questo è un esempio molto semplificato e l'ottimizzatore a volte non rimuove l'assegnazione del puntatore. Nascondere &m_data
dietro una funzione sembra "aiutare".
EDIT:
D: Come faccio a sapere che il compilatore è l'ottimizzazione a qualcosa come l'esempio fornito?
A: Io non sono molto bravo a leggere assembler, ho guardato comunque e ho fatto 3 osservazioni che mi fanno credere che si sta comportando in questo modo:
- Appena calci di ottimizzazione in (l'aggiunta di più assegnazioni di solito fa il trucco) l'assegnazione del puntatore non ha alcuna istruzione associata all'assemblatore. Inoltre non è stato spostato fino alla dichiarazione, quindi è davvero lasciato non inizializzato sembra (almeno per me).
- Nei casi in cui il programma si blocca, il debugger salta l'istruzione di assegnazione. Nei casi in cui il programma viene eseguito senza problemi, il debugger si ferma lì.
- Se guardo il contenuto del
pData
e il contenuto delm_data
durante il debug, mostra chiaramente che tutte le assegnazioni delif
-branch hanno effetto suim_data
em_data
riceve i valori corretti. Il puntatore stesso continua a puntare allo stesso valore non inizializzato che aveva fin dall'inizio. Pertanto, devo supporre che in effetti non stia usando il puntatore per effettuare i compiti.
Q: Devo fare qualcosa con i (Loop srotolamento)?
R: No, il vero programma utilizza effettivamente fare {...} while() per ciclare su uno SQL SELECT-gruppo di risultati in modo che il conteggio di iterazione è completamente runtime specifico e non può essere predeterminato dal compilatore.
Ridurre questo ad un [SSCCE] (http://sscce.org). – djechlin
@djechlin: i bug interessati dall'ottimizzazione potrebbero essere difficili da ridurre a campioni di codice ridotto. La domanda afferma esplicitamente che questo è già un esempio semplificato. –
non è ancora in grado di spiegare chiaramente un'idea, ma funziona allo stesso modo se si crea una classe invece se struct? – evilruff