Beberapa kompiler C hiper-modern akan menyimpulkan bahwa jika suatu program akan memanggil Perilaku Tidak Terdefinisi ketika diberi input tertentu, input seperti itu tidak akan pernah diterima. Akibatnya, kode apa pun yang tidak relevan kecuali jika input tersebut diterima dapat dihilangkan.
Sebagai contoh sederhana, diberikan:
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);
}
seorang kompiler dapat menyimpulkan bahwa karena evaluasi value >> (32-amount)
akan menghasilkan Perilaku Tidak Terdefinisi ketika amount
nol, fungsi blah
tidak akan pernah dipanggil dengan y
sama dengan nol; panggilan untuk foo
demikian bisa dibuat tanpa syarat.
Dari apa yang bisa saya katakan, filosofi ini tampaknya telah bertahan sekitar tahun 2010. Bukti paling awal yang saya lihat dari akarnya kembali ke tahun 2009, dan itu telah diabadikan dalam standar C11 yang secara eksplisit menyatakan bahwa jika Perilaku Tidak Terdefinisi terjadi pada setiap titik dalam pelaksanaan program, perilaku seluruh program secara surut menjadi tidak terdefinisi.
Apakah gagasan bahwa kompiler harus berusaha untuk menggunakan Perilaku Undefined untuk membenarkan optimasi reverse-kausal (yaitu Perilaku Undefined di rotateleft
fungsi harus menyebabkan compiler untuk mengasumsikan bahwa blah
harus telah disebut dengan non-nol y
, apakah tidak apa-apa akan pernah menyebabkan y
untuk memegang nilai tidak nol) secara serius diadvokasi sebelum 2009? Kapan hal seperti itu pertama kali diusulkan secara serius sebagai teknik optimasi?
[Tambahan]
Beberapa kompiler telah, bahkan di Abad ke-20, menyertakan opsi untuk memungkinkan beberapa jenis kesimpulan tentang loop dan nilai yang dihitung di dalamnya. Misalnya diberikan
int i; int total=0;
for (i=n; i>=0; i--)
{
doSomething();
total += i*1000;
}
kompiler, bahkan tanpa inferensi opsional, dapat menulis ulang sebagai:
int i; int total=0; int x1000;
for (i=n, x1000=n*1000; i>0; i--, x1000-=1000)
{
doSomething();
total += x1000;
}
karena perilaku kode itu akan sama persis dengan yang asli, bahkan jika kompilator menentukan bahwa int
nilai selalu membungkus mod-65536 mode dua-pelengkap . Opsi tambahan-inferensi akan membiarkan kompiler mengenali bahwa karena i
dan x1000
harus melewati nol pada saat yang sama, variabel sebelumnya dapat dihilangkan:
int total=0; int x1000;
for (x1000=n*1000; x1000 > 0; x1000-=1000)
{
doSomething();
total += x1000;
}
Pada sistem di mana int
nilai dibungkus mod 65536, upaya untuk menjalankan salah satu dari dua loop pertama dengan n
33 akan menghasilkan doSomething()
dipanggil 33 kali. Loop terakhir, sebaliknya, tidak akan meminta doSomething()
sama sekali, meskipun doa pertama doSomething()
akan mendahului aritmatika melimpah. Perilaku seperti mungkin dianggap "non-kausal", tetapi efek yang cukup baik dibatasi dan ada banyak kasus di mana perilaku akan terbukti berbahaya (dalam kasus di mana fungsi yang diperlukan untuk menghasilkan beberapa nilai ketika diberikan setiap masukan, tetapi nilai mungkin sewenang-wenang jika input tidak valid, memiliki loop selesai lebih cepat ketika diberi nilai tidak valid sebesarn
sebenarnya akan bermanfaat). Lebih lanjut, dokumentasi kompiler cenderung meminta maaf karena fakta bahwa itu akan mengubah perilaku program apa pun - bahkan yang terlibat di UB.
Saya tertarik ketika sikap penulis kompiler berubah dari gagasan bahwa platform harus ketika mendokumentasikan beberapa kendala perilaku yang dapat digunakan bahkan dalam kasus-kasus yang tidak diamanatkan oleh Standar, dengan gagasan bahwa setiap konstruksi yang akan bergantung pada perilaku yang tidak diamanatkan oleh Standar harus dicap tidak sah bahkan jika pada kebanyakan kompiler yang ada itu akan berfungsi dengan baik atau lebih baik daripada kode yang memenuhi persyaratan yang sama memenuhi persyaratan yang sama (sering memungkinkan optimasi yang tidak mungkin dilakukan dalam kode yang sepenuhnya mematuhi).
shape->Is2D()
dipanggil pada objek yang tidak diturunkan dari Shape2D
. Ada perbedaan besar antara mengoptimalkan kode yang hanya akan relevan jika Perilaku Undefined kritis telah terjadi dibandingkan kode yang hanya akan relevan dalam kasus-kasus di mana ...
Shape2D::Is2D
sebenarnya lebih baik daripada program yang layak.
int prod(int x, int y) {return x*y;}
akan mencukupi. Mematuhi" jangan meluncurkan nuklir "dengan cara yang benar-benar sesuai, bagaimanapun, akan memerlukan kode yang lebih sulit untuk dibaca dan hampir akan tentu berjalan jauh lebih lambat di banyak platform