Alcuni compilatori C ultramoderni dedurranno che se un programma invoca il comportamento non definito quando vengono dati determinati input, tali input non verranno mai ricevuti. Di conseguenza, qualsiasi codice che sarebbe irrilevante a meno che tali input siano ricevuti può essere eliminato.
Come esempio semplice, dato:
void foo(uint32_t);
uint32_t rotateleft(uint_t value, uint32_t amount)
{
return (value << amount) | (value >> (32-amount));
}
uint32_t blah(uint32_t x, uint32_t y)
{
if (y != 0) foo(y);
return rotateleft(x,y);
}
un compilatore può dedurre che poiché la valutazione di value >> (32-amount)
darà un comportamento non definito quando amount
è zero, la funzione blah
non sarà mai chiamata con y
uguale a zero; la chiamata a foo
può quindi essere resa incondizionata.
Da quello che posso dire, questa filosofia sembra essersi impadronita intorno al 2010. La prima prova che ho visto delle sue radici risale al 2009, ed è stata sancita nello standard C11 che afferma esplicitamente che se il comportamento non definito si verifica in qualsiasi punto dell'esecuzione di un programma, il comportamento dell'intero programma diventa retroattivamente indefinito.
L'idea che i compilatori dovessero tentare di utilizzare il comportamento non definito per giustificare le ottimizzazioni causali inverse (ovvero il comportamento non definito nella funzione rotateleft
dovrebbe far assumere al compilatore che blah
deve essere stato chiamato con un valore diverso da zero y
, indipendentemente dal fatto che qualcosa potrebbe mai causare y
per mantenere un valore diverso da zero) seriamente difeso prima del 2009? Quando mai una cosa del genere è stata proposta seriamente come tecnica di ottimizzazione?
[Addendum]
Alcuni compilatori hanno incluso, anche nel ventesimo secolo, opzioni per abilitare determinati tipi di inferenze sui loop e sui valori calcolati al loro interno. Ad esempio, dato
int i; int total=0;
for (i=n; i>=0; i--)
{
doSomething();
total += i*1000;
}
un compilatore, anche senza le inferenze facoltative, potrebbe riscriverlo come:
int i; int total=0; int x1000;
for (i=n, x1000=n*1000; i>0; i--, x1000-=1000)
{
doSomething();
total += x1000;
}
poiché il comportamento di quel codice corrisponderebbe esattamente all'originale, anche se il compilatore specificava che i valori di int
si sovrappongono sempre in mod-65536 moda a complemento a due . L'opzione di inferenza aggiuntiva consentirebbe al compilatore di riconoscere che dal momento che i
e x1000
devono attraversare lo zero allo stesso tempo, la variabile precedente può essere eliminata:
int total=0; int x1000;
for (x1000=n*1000; x1000 > 0; x1000-=1000)
{
doSomething();
total += x1000;
}
In un sistema in cui int
ha eseguito il wrapping del mod 65536, un tentativo di eseguire uno dei primi due loop con n
uguale a 33 risulterebbe in doSomething()
invocato 33 volte. L'ultimo ciclo, al contrario, non invocerebbe affatto doSomething()
, anche se la prima chiamata di doSomething()
avrebbe preceduto qualsiasi overflow aritmetico. Un simile comportamento potrebbe essere considerato "non causale", ma gli effetti sono ragionevolmente ben vincolati e ci sono molti casi in cui il comportamento sarebbe inequivocabilmente innocuo (nei casi in cui è richiesta una funzione per produrre un certo valore quando viene data qualsiasi input, ma il valore può essere arbitrario se l'input non è valido, avendo la fine del ciclo più veloce quando viene dato un valore non valido di n
sarebbe effettivamente vantaggioso). Inoltre, la documentazione del compilatore tendeva a scusarsi per il fatto che avrebbe cambiato il comportamento di qualsiasi programma, anche quelli che erano impegnati in UB.
Sono interessato a quando gli atteggiamenti degli scrittori di compilatori si sono allontanati dall'idea che le piattaforme dovrebbero quando sono pratici documentare alcuni limiti comportamentali utilizzabili anche nei casi non imposti dallo Standard, all'idea che qualsiasi costrutto che si baserebbe su qualsiasi comportamento non mandato dallo Standard dovrebbe essere marchiato illegittimo anche se sulla maggior parte dei compilatori esistenti funzionerebbe bene o meglio di qualsiasi codice rigorosamente conforme che soddisfi gli stessi requisiti (spesso consentendo ottimizzazioni che non sarebbero possibili in un codice rigorosamente conforme).