Tanımlanmamış davranışa sahip dalların erişilemez olduğu ve ölü kod olarak optimize edilebileceği varsayılabilir mi?


89

Şu ifadeyi düşünün:

*((char*)NULL) = 0; //undefined behavior

Açıkça tanımlanmamış davranışa neden olur. 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?

Kullanıcının numarayı hiç girmemesi durumunda aşağıdaki program iyi tanımlanmış olur 3mu?

while (true) {
 int num = ReadNumberFromConsole();
 if (num == 3)
  *((char*)NULL) = 0; //undefined behavior
}

Yoksa kullanıcı ne girerse girsin, tamamen tanımlanmamış bir davranış mı?

Ayrıca, derleyici tanımsız davranışın çalışma zamanında asla çalıştırılmayacağını varsayabilir mi? Bu, zamanda geriye doğru akıl yürütmeye izin verir:

int num = ReadNumberFromConsole();

if (num == 3) {
 PrintToConsole(num);
 *((char*)NULL) = 0; //undefined behavior
}

Burada derleyici num == 3, her zaman tanımsız davranışı çağıracağımızı düşünebilir . Bu nedenle bu durum imkansız olmalı ve numaranın basılmasına gerek yoktur. Tüm ififade optimize edilebilir. Standarda göre bu tür geriye doğru akıl yürütmeye izin veriliyor mu?


19
Bazen çok sayıda temsilcisi olan kullanıcıların sorulara daha fazla olumlu oy verip vermediğini merak ediyorum, çünkü "çok fazla temsilcisi var, bu iyi bir soru olmalı" ... ama bu durumda soruyu okudum ve "vay, bu harika "soruyu sorana bakmadan önce.
turbulencetoo

4
Zamanın düşünüyorum tanımsız davranış ortaya tanımlanmadı.
eerorika

6
C ++ standardı, herhangi bir noktada tanımsız davranışa sahip bir yürütme yolunun tamamen tanımsız olduğunu açıkça belirtir. Hatta bunu, yolda tanımsız davranışa sahip herhangi bir programın tamamen tanımsız olduğunu (diğer bölümlerde makul sonuçlar içerir, ancak garanti edilmediğini) söyleyerek yorumlayabilirim. Derleyiciler, programınızı değiştirmek için tanımsız davranışı kullanmakta özgürdür. blog.llvm.org/2011/05/what-every-c-programmer-should-know.html bazı güzel örnekler içerir.
Jens

4
@Jens: Gerçekten sadece yürütme yolu anlamına geliyor. Aksi takdirde başınız dertte demektir const int i = 0; if (i) 5/i;.
MSalters

1
Derleyici genel olarak bunun aramadığını ispatlayamaz, PrintToConsolebu std::exityüzden aramayı yapmak zorundadır.
MSalters

Yanıtlar:


66

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ü pnull 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-checksstandartları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".


4
Aslında geçmişte bundan dolayı epeyce güvenlik sorunu vardı. Özellikle, herhangi bir gerçek sonrası taşma denetimi, bundan dolayı optimize edilme tehlikesiyle karşı karşıyadır. Örneğin void can_add(int x) { if (x + 100 < x) complain(); }eğer, çünkü tamamen uzak optimize edilebilir x+100 doesn' taşma hiçbir şey olmuyor ve eğer x+100 does hiçbir şey öylesine, UB standardına göre var taşması, belki olur.
fgp

3
@fgp: doğru, bu, insanların takılıp düşerlerse acı bir şekilde şikayet ettikleri bir optimizasyondur, çünkü derleyicinin sizi cezalandırmak için kasıtlı olarak kodunuzu kırdığını hissetmeye başlar. "Kaldırmanı istiyorsam neden bu şekilde yazayım!" ;-) Ama bazen optimizer için daha büyük aritmetik ifadeleri işlerken, taşma olmadığını varsaymanın ve yalnızca bu durumlarda gerekli olabilecek pahalı şeylerden kaçınmanın yararlı olduğunu düşünüyorum.
Steve Jessop

2
Kullanıcı hiçbir zaman 3 girmezse, ancak bir yürütme sırasında 3 girerse tüm yürütme tanımsız hale gelirse programın tanımsız olmadığını söylemek doğru olur mu? Programın tanımsız davranışı başlatacağı% 100 kesinleştiğinde (ve bundan daha erken olmayacak şekilde) davranışa herhangi bir şey olmasına izin verilir. Bu ifadelerim% 100 doğru mu?
usr

3
@usr: Bunun doğru olduğuna inanıyorum, evet. Sizin özel örneğinizle (ve işlenmekte olan verilerin kaçınılmazlığı hakkında bazı varsayımlarda bulunarak), bir uygulamanın prensipte, 3eğer isterse arabelleğe alınmış STDIN'de ileriye bakabileceğini ve birini görür görmez eve geri dönebileceğini düşünüyorum. gelen.
Steve Jessop

3
Fazladan 1 (Elimde olsa) sizin PS için
Fred Larson

10

1.9 / 4'teki standart durumlar

[Not: Bu Uluslararası Standart, tanımlanmamış davranışlar içeren programların davranışına herhangi bir gereklilik getirmez. - son not]

İlginç olan nokta muhtemelen "içerme" nin ne anlama geldiğidir. 1.9 / 5'te biraz sonra şunu belirtir:

Bununla birlikte, bu tür bir yürütme tanımlanmamış bir işlem içeriyorsa, bu Uluslararası Standart, bu programı bu girdi ile yürüten uygulamaya herhangi bir gereklilik getirmez (ilk tanımsız işlemden önceki işlemlerle ilgili olarak bile)

Burada özellikle "bu girişle ... yürütme" den bahsediyor. Bunu şu anda yürütülmeyen olası bir daldaki tanımsız davranışın mevcut yürütme dalını etkilemediği şeklinde yorumluyorum.

Ancak farklı bir sorun, kod üretimi sırasında tanımlanmamış davranışa dayalı varsayımlardır. Bununla ilgili daha fazla ayrıntı için Steve Jessop'un cevabına bakın.


1
Kelimenin tam anlamıyla alınırsa, var olan tüm gerçek programlar için ölüm cezası budur.
usr

6
Sorunun, UB'nin koda ulaşılmadan önce görünüp görünmeyeceği olduğunu sanmıyorum . Soru, anladığım kadarıyla, koda ulaşılamazsa UB'nin görünüp görünmeyeceğiydi. Ve elbette bunun cevabı "hayır".
sepp2k

Standart bu konuda 1.9 / 4'te o kadar net değil, ancak 1.9 / 5 muhtemelen sizin söyledikleriniz olarak yorumlanabilir.
Danvil

1
Notlar normatif değildir. 1.9 / 5, 1.9 / 4'te nota üstün geldi
MSalters

5

Öğretici bir örnek

int foo(int x)
{
    int a;
    if (x)
        return a;
    return 0;
}

Hem mevcut GCC hem de mevcut Clang, bunu (x86'da) optimize edecek

xorl %eax,%eax
ret

onlar çünkü anlamak o xzaman sıfır olduğu içinde UB gelen if (x)denetim yolu. GCC size başlatılmamış değer kullanımı uyarısı bile vermez! (çünkü yukarıdaki mantığı uygulayan geçiş, başlatılmamış değer uyarıları oluşturan geçişten önce çalışır)


1
İlginç bir örnek. Optimizasyonu etkinleştirmenin uyarıyı gizlemesi oldukça kötü. Bu bile belgelenmiyor - GCC belgeleri yalnızca optimizasyonu etkinleştirmenin daha fazla uyarı ürettiğini söylüyor .
sleske

@sleske Çok kötü, kabul ediyorum, ancak başlatılmamış değer uyarılarını "doğru yapmak" çok zordur - bunları mükemmel bir şekilde yapmak Durma Problemi ile eşdeğerdir ve programcılar yanlış pozitifleri susturmak için "gereksiz" değişken başlatmaları ekleme konusunda garip bir şekilde mantıksız davranırlar. bu yüzden derleyici yazarları bir varilin üstesinden gelir. Eskiden GCC'yi hacklerdim ve herkesin başlatılmamış değer uyarı geçişiyle uğraşmaktan korktuğunu hatırlıyorum.
zwol

@zwol: Bu tür ölü kodların ortadan kaldırılmasından kaynaklanan "optimizasyonun" ne kadarının aslında yararlı kodu küçülttüğünü ve programcıların kodu büyütmesine ne kadar yol açtığını merak ediyorum (örneğin a, her koşulda olsa bile başlatılacak kod ekleyerek) başlatılmamış a, işlevin hiçbir zaman onunla hiçbir şey yapmayacağı işleve geçirilir)?
supercat

@supercat ~ 10 yıldır derleyici çalışmalarına derinlemesine dahil olmadım ve oyuncak örneklerinden optimizasyonlar hakkında mantık yürütmek neredeyse imkansız. Bu tür bir optimizasyon, doğru hatırlıyorsam, gerçek uygulamalarda% 2-5 genel kod boyutu küçültme ile ilişkili olma eğilimindedir.
zwol

1
@supercat% 2-5 bu işler ilerledikçe çok büyük . İnsanların% 0.1 oranında terlediğini gördüm.
zwol

4

Mevcut C ++ çalışma taslağı 1.9.4'te şunu söylüyor:

Bu Uluslararası Standart, tanımlanmamış davranış içeren programların davranışına herhangi bir gereklilik getirmez.

Buna dayanarak, herhangi bir yürütme yolunda tanımsız davranış içeren bir programın, her çalıştırıldığında her şeyi yapabileceğini söyleyebilirim.

Tanımlanmamış davranışlar ve derleyicilerin genellikle yaptıkları hakkında gerçekten iyi iki makale var:


1
Bu hiç mantıklı değil. İşlev , elbette tanımsız int f(int x) { if (x > 0) return 100/x; else return 100; }olsa bile, kesinlikle tanımlanmamış bir davranışı asla çağırmaz 100/0.
fgp

1
gerçi, tanımsız davranış eğer yani standart (özellikle 1.9 / 5) diyor ne @fgp olabilir ulaşılabilir, bu önemli değil ne zaman o ulaşılır. Örneğin, hiçbir şey yazdırmanız garanti printf("Hello, World"); *((char*)NULL) = 0 edilmez . Bu, optimizasyona yardımcı olur, çünkü derleyici, tanımlanmamış davranışları hesaba katmak zorunda kalmadan, sonunda gerçekleşeceğini bildiği işlemleri serbestçe yeniden sıralayabilir (tabii ki bağımlılık kısıtlamalarına tabidir).
fgp

Fonksiyonunuza sahip bir programın tanımsız davranış içermediğini söyleyebilirim çünkü 100/0 değerinin değerlendirileceği bir girdi yoktur.
Jens

1
Kesinlikle - yani önemli olan UB'nin gerçekten tetiklenip tetiklenemeyeceğidir, teorik olarak tetiklenip tetiklenemeyeceği değil. Veya int x,y; std::cin >> x >> y; std::cout << (x+y);"1 + 1 = 17" demesine izin verildiğini iddia etmeye hazır mısınız , çünkü taşan bazı girdiler var x+y(ki bu UB, çünkü intişaretli bir türdür).
fgp

Resmi olarak, programın tanımsız davranışa sahip olduğunu söyleyebilirim çünkü onu tetikleyen girdiler vardır. Ancak bunun C ++ bağlamında bir anlam ifade etmediği konusunda haklısınız, çünkü tanımsız davranışlar olmadan bir program yazmak imkansız olurdu. C ++ 'da daha az tanımsız davranış olduğunda hoşuma gider, ancak dil böyle çalışmaz (ve bunun için bazı iyi nedenler vardır, ancak bunlar günlük kullanımımı ilgilendirmez ...).
Jens

3

"Davranış" kelimesi, bir şeyin yapıldığı anlamına gelir . Asla yürütülmeyen bir durum "davranış" değildir.

Bir örnek:

*ptr = 0;

Bu tanımlanmamış davranış mı? ptr == nullptrProgramın yürütülmesi sırasında en az bir kez % 100 emin olduğumuzu varsayalım . Cevap evet olmalıdır.

Peki buna ne dersin?

 if (ptr) *ptr = 0;

Bu tanımsız mı? ( ptr == nullptrEn az bir kez hatırla ?) Umarım hayır, aksi takdirde hiçbir yararlı program yazamazsın.

Bu cevabın verilmesinde hiçbir Srandardlı zarar görmedi.


3

Tanımlanmamış davranış, program daha sonra ne olursa olsun tanımsız davranışa neden olduğunda ortaya çıkar. Ancak aşağıdaki örneği verdiniz.

int num = ReadNumberFromConsole();

if (num == 3) {
 PrintToConsole(num);
 *((char*)NULL) = 0; //undefined behavior
}

Derleyici tanımını bilmediği sürece koşulluyu PrintToConsolekaldıramaz if (num == 3). LongAndCamelCaseStdio.hAşağıdaki beyanı ile sistem başlığınız olduğunu varsayalım PrintToConsole.

void PrintToConsole(int);

Pek yardımcı olacak bir şey yok, tamam. Şimdi, bu işlevin gerçek tanımını kontrol ederek satıcının ne kadar kötü (veya belki de o kadar kötü olmayan, tanımlanmamış davranış daha kötü olabilirdi) görelim.

int printf(const char *, ...);
void exit(int);

void PrintToConsole(int num) {
    printf("%d\n", num);
    exit(0);
}

Derleyici aslında derleyicinin ne yaptığını bilmediği herhangi bir keyfi işlevin çıkabileceğini veya bir istisna atabileceğini varsaymalıdır (C ++ durumunda). *((char*)NULL) = 0;Çağrıdan sonra yürütme devam etmeyeceği için bunun yürütülmeyeceğini fark PrintToConsoleedebilirsiniz.

Tanımlanmamış davranış, PrintToConsolegerçekte geri döndüğünde dikkat çeker. Derleyici bunun olmamasını bekler (çünkü bu, programın ne olursa olsun tanımsız davranışı yürütmesine neden olur), bu nedenle her şey olabilir.

Ancak, başka bir şey düşünelim. Diyelim ki boş kontrol yapıyoruz ve değişkeni sıfır kontrolünden sonra kullanıyoruz.

int putchar(int);

const char *warning;

void lol_null_check(const char *pointer) {
    if (!pointer) {
        warning = "pointer is null";
    }
    putchar(*pointer);
}

Bu durumda, lol_null_checkNULL olmayan bir işaretçi gerektirdiğini fark etmek kolaydır . Global uçucu olmayan warningdeğişkene atama , programdan çıkabilecek veya herhangi bir istisna oluşturabilecek bir şey değildir. pointerSihirli (eğer olursa, o tanımsız bir davranış) işlevi ortasında değerini değiştirmez, böylece de uçucu değildir. Çağırma lol_null_check(NULL), değişkenin atanmamasına neden olabilecek tanımsız davranışa neden olur (çünkü bu noktada, programın tanımsız davranışı yürüttüğü bilinmektedir).

Ancak, tanımlanmamış davranış, programın her şeyi yapabileceği anlamına gelir. Bu nedenle, hiçbir şey tanımlanmamış davranışın zamanda geriye gitmesini ve programınızın ilk int main()yürütme satırından önce çökmesini engellemez . Tanımlanmamış bir davranış, mantıklı olması gerekmiyor. 3 yazdıktan sonra da çökebilir, ancak tanımlanmamış davranış zamanda geri dönecek ve siz 3 yazmadan önce çökecektir. Ve kim bilir, belki de tanımlanmamış davranış sistem RAM'inizin üzerine yazacak ve sisteminizin 2 hafta sonra çökmesine neden olacaktır, tanımsız programınız çalışmıyorken.


Tüm geçerli noktalar. PrintToConsoleçökmelerden sonra bile görülebilen ve güçlü bir şekilde sıralanan bir program-harici yan etkisi ekleme girişimim. Bu ifadenin optimize edilip edilmediğinden emin olabileceğimiz bir durum yaratmak istedim. Ama asla geri dönmeyeceği konusunda haklısın .; Global'e yazma örneğiniz, UB ile ilgisi olmayan diğer optimizasyonlara tabi olabilir. Örneğin kullanılmayan bir global silinebilir. Kontrolü geri getirmesi garantili bir şekilde harici bir yan etki yaratmak için bir fikriniz var mı?
usr

Herhangi bir dış dünyada gözlemlenebilir yan etki, bir derleyicinin geri dönüşleri kabul etmekte özgür olacağı kod tarafından üretilebilir mi? Anladığım kadarıyla, basitçe bir volatiledeğişkeni okuyan bir yöntem bile geçerli iş parçacığını anında kesintiye uğratabilecek bir G / Ç işlemini yasal olarak tetikleyebilir; kesme işleyicisi, başka bir şey yapma şansı bulamadan iş parçacığını öldürebilir. Derleyicinin bu noktadan önce tanımsız davranışı zorlayabileceği bir gerekçe göremiyorum.
supercat

C standardı açısından, Tanımsız Davranış'ın bilgisayarın programın önceki eylemlerinin tüm kanıtlarını bulup yok edecek bazı kişilere bir mesaj göndermesine neden olmasının yasa dışı hiçbir şey olmayacaktır, ancak bir eylem bir iş parçacığını sonlandırabilirse, o zaman bu eylemden önce sıralanan her şey, ondan sonra meydana gelen Tanımlanmamış Davranışlardan önce gerçekleşmelidir.
supercat

1

Program tanımlanmamış davranışı çağıran bir ifadeye ulaşırsa, programın herhangi bir çıktısına / davranışına herhangi bir gereksinim getirilmez; tanımsız davranış çağrıldığında "önce" veya "sonra" gerçekleşmeleri önemli değildir.

Her üç kod parçacığı hakkındaki mantığınız doğru. Özellikle, bir derleyici, tanımlanmamış davranışları koşulsuz __builtin_unreachable()olarak GCC'nin davrandığı şekilde çağıran herhangi bir ifadeyi , ifadenin erişilemez olduğuna (ve dolayısıyla koşulsuz olarak ona giden tüm kod yollarının da erişilemez olduğuna) dair bir optimizasyon ipucu olarak ele alabilir . Diğer benzer optimizasyonlar elbette mümkündür.


1
Merak ettiğim için, zaman içinde __builtin_unreachable()hem ileri hem de geri ilerleyen etkiler ne zaman başladı? extern volatile uint32_t RESET_TRIGGER; void RESET(void) { RESET_TRIGGER = 0xAA55; __memorybarrier(); __builtin_unreachable(); }Benim gibi bir şey verildiğinde builtin_unreachable(), derleyiciye returntalimatı ihmal edebileceğini bildirmenin iyi olduğunu görebilir , ancak bu önceki kodun atlanabileceğini söylemekten oldukça farklı olacaktır.
10'da süper araba

@supercat RESET_TRIGGER uçucu olduğundan, bu konuma yazma keyfi yan etkilere neden olabilir. Derleyiciye göre opak bir yöntem çağrısı gibidir. Bu nedenle, __builtin_unreachableulaşılan kanıtlanamaz (ve durum böyle değildir) . Bu program tanımlanmıştır.
usr

@usr: Düşük seviyeli derleyicilerin uçucu erişimleri opak yöntem çağrıları olarak ele alması gerektiğini düşünüyorum, ancak ne clang ne de gcc bunu yapmıyor. Diğer şeylerin yanı sıra, opak bir yöntem çağrısı, adresi dış dünyaya açık olan ve canlı bir restrictişaretçi tarafından erişilmeyen veya erişilemeyen herhangi bir nesnenin tüm baytlarının bir unsigned char*.
supercat

@usr: Bir derleyici geçici bir erişimi, maruz kalan nesnelere erişimle ilgili olarak opak bir yöntem çağrısı olarak değerlendirmezse, başka amaçlar için bunu yapmasını beklemek için özel bir neden görmüyorum. Standart, uygulamaların bunu yapmasını gerektirmez, çünkü bir derleyicinin uçucu bir erişimden tüm olası etkileri bilebileceği bazı donanım platformları vardır. Bununla birlikte, gömülü kullanıma uygun bir derleyici, geçici erişimlerin, derleyici yazılırken icat edilmemiş donanımı tetikleyebileceğini anlamalıdır.
supercat

@supercat Sanırım haklısın. Uçucu işlemlerin "soyut makine üzerinde hiçbir etkisi" olmadığı ve bu nedenle programı sonlandıramayacağı veya yan etkilere neden olamayacağı görülmektedir.
usr

1

Pek çok şey için pek çok standart, IETF RFC 2119'da tanımlanana benzer bir terminoloji kullanarak (bu belgedeki tanımlara mutlaka atıfta bulunulmasa da) uygulamaların YAPMASI GEREKEN veya YAPMAMASI GEREKEN şeyleri açıklamak için çok çaba harcar . Çoğu durumda, uygulamaların yararsız veya pratik olmayacakları durumlar dışında yapması gereken şeylerin açıklamaları, tüm uyumlu uygulamaların uyması gereken gereksinimlerden daha önemlidir .

Ne yazık ki, C ve C ++ Standartları,% 100 gerekli olmasa da, ters davranışları belgelemeyen kaliteli uygulamalardan beklenilmesi gereken şeylerin tanımlarından kaçınma eğilimindedir. Uygulamaların bir şeyler yapması gerektiğine dair bir öneri, belirli bir uygulamada, olmayanların daha düşük olmadığını ve hangi davranışların kullanışlı veya pratik olacağının, pratik olmayan ve yararsız olacağının genellikle açık olduğu durumlarda, belirli bir uygulamada, Standardın bu tür yargılara müdahale etmesine çok az ihtiyaç duyulmaktadır.

Akıllı bir derleyici, Kodun kaçınılmaz olarak Tanımsız Davranışa neden olacak girdileri aldığı, ancak "zeki" ve "aptalca" zıt anlamlı olmayanlar dışında hiçbir etkisi olmayan herhangi bir kodu ortadan kaldırırken, Standard'a uyabilir. Standardın yazarlarının, belirli bir durumda yararlı bir şekilde davranmanın yararsız ve pratik olmayacağı bazı türden uygulamaların olabileceğine karar vermiş olması, bu tür davranışların başkaları için pratik ve yararlı olarak kabul edilip edilmeyeceği konusunda herhangi bir yargıya işaret etmez. Bir uygulama, bir "ölü dallı" budama fırsatının kaybının ötesinde hiçbir maliyet olmadan davranışsal bir garantiyi destekleyebiliyorsa, bu garantiden elde edilebilecek hemen hemen her değer kullanıcı kodu, bunu sağlama maliyetini aşacaktır. Ölü dalların ortadan kaldırılması, yapılmayacağı durumlarda iyi olabilir.Belirli bir durum kullanıcı kodu neredeyse olası davranışları ele olabilirdi eğer, ancak diğer ölü şube eleme daha herhangi bir çaba kullanıcı kodu UB olasılıkla DBE ulaşılan değeri aşacak önlemek için harcamak zorunda kalacaktı.


UB'den kaçınmanın kullanıcı koduna bir maliyet getirebileceği iyi bir noktadır .
usr

@usr: Modernistlerin tamamen gözden kaçırdığı bir nokta. Bir örnek eklemeli miyim? Örneğin, kodun x*y < zne zaman taşmadığını değerlendirmesi gerekiyorsa ve x*ytaşma durumunda keyfi şekilde ancak yan etkiler olmadan 0 veya 1 vermesi durumunda, çoğu platformda ikinci ve üçüncü gereksinimleri karşılamanın neden daha pahalı olması gerektiğine dair hiçbir neden yoktur. ilkini karşılamak, ancak her durumda Standart tanımlı davranışı garanti etmek için ifadeyi yazmanın herhangi bir yolu, bazı durumlarda önemli maliyetler ekleyecektir. İfadeyi yazmak, (int64_t)x*y < zhesaplama maliyetini dört katından fazla
artırabilir

... bazı platformlarda ve (int)((unsigned)x*y) < zbir derleyicinin başka türlü yararlı cebirsel ikameler kullanmasını engelleyeceği gibi yazmak (örneğin, bunu biliyorsa xve zeşit ve pozitifse, orijinal ifadeyi basitleştirebilir y<0, ancak sürüm işaretsiz derleyiciyi çarpma işlemi yapmaya zorlar). Derleyici, Standart bunu zorunlu kılmasa bile garanti edebiliyorsa, "yan etki olmaksızın 0 veya 1 ver" gereksinimini sürdürür, kullanıcı kodu derleyiciye başka türlü elde edemeyeceği optimizasyon fırsatlarını verebilir.
supercat

Evet, görünen o ki, tanımlanmamış davranışların daha hafif bir biçimi burada yardımcı olabilir. Programcı x*y, taşma durumunda normal bir değer, ancak herhangi bir değer yayınlamasına neden olan bir modu açabilir . C / C ++ 'da yapılandırılabilir UB benim için önemli görünüyor.
usr

@usr: C89 Standardının yazarları, imzalanmamış kısa değerlerin imzalanmaya teşvik edilmesinin en ciddi kırılma değişikliği olduğunu söylerken dürüst olsalardı ve cahil aptallar değillerse, bu, platformların yararlı davranış garantileri tanımlarken, bu tür platformlar için uygulamalar bu garantileri programcılara açık hale getiriyordu ve programcılar bunları kullanıyordu, bu tür platformların derleyicileri , Standardın emredip emretmemesine bakılmaksızın bu tür davranış garantileri sunmaya devam edeceklerdi .
supercat
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.