Belirli bir programda böyle bir ifadenin varlığı, tüm programın tanımsız olduğu veya davranışın yalnızca kontrol akışı bu ifadeye ulaştığında tanımsız hale geldiği anlamına mı gelir?
Hiçbiri. İlk koşul çok güçlü ve ikincisi çok zayıf.
Nesne erişimi bazen sıralıdır, ancak standart, programın zaman dışındaki davranışını tanımlar. Danvil zaten alıntı yaptı:
Böyle bir yürütme tanımlanmamış bir işlem içeriyorsa, bu Uluslararası Standart, bu programın bu girdi ile çalıştırılmasına ilişkin hiçbir gereklilik getirmez (ilk tanımsız işlemden önceki işlemlerle ilgili olarak bile)
Bu şu şekilde yorumlanabilir:
Programın yürütülmesi tanımlanmamış bir davranışa neden olursa, tüm programın tanımsız davranışı vardır.
Yani, UB ile ulaşılamaz bir ifade programa UB vermez. (Girişlerin değerleri nedeniyle) asla ulaşılmayan ulaşılabilir bir ifade, programa UB vermez. Bu yüzden ilk kondisyonunuz çok güçlü.
Şimdi, derleyici genel olarak UB'nin ne olduğunu söyleyemez. Dolayısıyla, optimize edicinin, davranışlarının tanımlanması durumunda yeniden sıralanabilecek potansiyel UB içeren ifadeleri yeniden sıralayabilmesi için, UB'nin "zamanda geri dönmesine" ve önceki sıra noktasından önce (veya C) yanlış gitmesine izin vermek gerekir. ++ 11 terminolojisi, UB'nin UB şeyinden önce sıralanan şeyleri etkilemesi için). Bu nedenle ikinci durumunuz çok zayıf.
Bunun başlıca bir örneği, optimize edicinin katı örtüşme işlemine dayandığı zamandır. Katı örtüşme kurallarının tüm amacı, derleyicinin, söz konusu işaretçilerin aynı belleğe takma ad vermesi mümkün olsaydı, geçerli bir şekilde yeniden sıralanamayacak işlemleri yeniden düzenlemesine izin vermektir. Dolayısıyla, yasadışı olarak örtüşme işaretçileri kullanırsanız ve UB oluşursa, UB ifadesinden "önceki" bir ifadeyi kolayca etkileyebilir. Soyut makine söz konusu olduğunda, UB ifadesi henüz çalıştırılmadı. Asıl nesne kodu söz konusu olduğunda, kısmen veya tamamen yürütülmüştür. Ancak standart, optimize edicinin ifadeleri yeniden düzenlemesinin ne anlama geldiğine veya bunun UB için sonuçlarının ne olduğuna dair ayrıntılara girmeye çalışmaz. Sadece uygulama ruhsatının istediği anda ters gitmesini sağlar.
Bunu "UB'nin bir zaman makinesi var" olarak düşünebilirsiniz.
Özellikle örneklerinize cevap vermek için:
- Davranış, yalnızca 3 okunursa tanımsızdır.
- Temel bir blok tanımsız olduğu kesin olan bir işlem içeriyorsa, derleyiciler kodu ölü olarak ortadan kaldırabilir ve yapar. Temel bir blok olmayan ancak tüm dalların UB'ye yol açtığı durumlarda izin verilir (ve tahmin ediyorum). Bu örnek, bir
PrintToConsole(3)
şekilde geri döneceğinden emin olmadıkça bir aday değildir . Bir istisna falan atabilir.
İkincinize benzer bir örnek -fdelete-null-pointer-checks
, şu şekilde kod alabilen gcc seçeneğidir (bu özel örneği kontrol etmedim, genel fikri açıklayıcı olarak düşünün):
void foo(int *p) {
if (p) *p = 3;
std::cout << *p << '\n';
}
ve şu şekilde değiştirin:
*p = 3;
std::cout << "3\n";
Neden? Çünkü p
null ise , o zaman kodun yine de UB'si vardır, bu yüzden derleyici onun boş olmadığını varsayabilir ve buna göre optimize edebilir. Linux çekirdeği bunun üzerine açıldı ( https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-1897 ) esasen bir boş göstericiye başvurunun kaldırılmasının gerekmediği bir modda çalıştığı için UB olması durumunda, çekirdeğin işleyebileceği tanımlanmış bir donanım istisnasıyla sonuçlanması beklenir. Optimizasyon etkinleştirildiğinde, gcc, -fno-delete-null-pointer-checks
standartların ötesinde garantiyi sağlamak için 'nin kullanılmasını gerektirir .
Not: "Tanımlanmamış davranış ne zaman ortaya çıkar?" Sorusunun pratik yanıtı. "gün için ayrılmayı planladığınızdan 10 dakika önce".