Bu bir Clang hatası
... sonsuz döngü içeren bir işlevi satır içine alırken. Davranış while(1);
doğrudan anada göründüğünde farklıdır , bu da bana çok adamcağız kokuyor.
Özet ve bağlantılar için @ Arnavion'un cevabına bakınız . Bu cevabın geri kalanı, bilinen bir hata olsun, bunun bir hata olduğunu onaylamadan önce yazılmıştı.
Başlık sorusunu cevaplamak için: Optimize edilmeyen sonsuz bir boş döngüyü nasıl yapabilirim? ? -
yapmak die()
bir makro değil, bir işlev , Clang 3.9 ve daha sonra bu hatayı gidermek için. (Daha önceki Clang sürümleri döngüyü tutar veyacall
sonsuz döngü ile işlevin satıriçi olmayan bir sürümüne yayar .) print;while(1);print;
İşlev , arayana ( Godbolt ) girdiğinde bile güvenli görünüyor . -std=gnu11
vs. -std=gnu99
hiçbir şeyi değiştirmez.
Sadece GNU C ile ilgileniyorsanız__asm__("");
, döngü içindeki P__J __ 'ler de çalışır ve onu anlayan herhangi bir derleyici için herhangi bir çevre kodunun optimizasyonuna zarar vermemelidir. GNU C Basic asm ifadeleri dolaylı olarakvolatile
, bu nedenle bu C soyut makinesinde olduğu gibi "yürütülmesi" gereken görünür bir yan etki olarak sayılır. (Ve evet, Clang, GCC kılavuzunda belgelendiği gibi C'nin GNU lehçesini uygular.)
Bazı insanlar boş bir sonsuz döngüyü optimize etmenin yasal olabileceğini öne sürdüler. Katılmıyorum 1 , ama biz bunu kabul bile, o olamaz da Clang döngü sonra ifadeleri ulaşılamaz olduğunu varsayıyorum, için yasal olması ve bir sonraki fonksiyonu içine fonksiyonun sonuna kapalı infaz düşürdüm veya çöpe rastgele talimatlar olarak çözülür.
(Bu Clang ++ için standartlara uygun olacaktır (ancak yine de çok yararlı değildir); herhangi bir yan etkisi olmayan sonsuz döngüler C ++ 'da UB'dir, ancak C değildir.
(1); C? UB'deki tanımlanmamış davranış derleyicinin temelde herhangi bir şey yayınlamasına izin verir UB ile karşılaşacak bir yürütme yolu üzerindeki kod asm
için Döngüdeki bir ifade, C ++ için bu UB'yi önleyecektir.Ancak pratikte, C ++ olarak derlenen Clang, satır içi olanlar hariç, sabit ifade sonsuz boş döngüleri kaldırmaz. C olarak derleme)
Manuel olarak satır içine alma, while(1);
Clang'ın derleme şeklini değiştirir: asm'de sonsuz döngü vardır. Kurallar avukatý POV'dan beklediđimiz bu.
#include <stdio.h>
int main() {
printf("begin\n");
while(1);
//infloop_nonconst(1);
//infloop();
printf("unreachable\n");
}
Godbolt derleyici gezgininde Clang 9.0-O3 -xc
, x86-64 için C ( ) olarak derlenir :
main: # @main
push rax # re-align the stack by 16
mov edi, offset .Lstr # non-PIE executable can use 32-bit absolute addresses
call puts
.LBB3_1: # =>This Inner Loop Header: Depth=1
jmp .LBB3_1 # infinite loop
.section .rodata
...
.Lstr:
.asciz "begin"
Aynı seçeneklere sahip aynı derleyici, önce aynı main
çağrıyı çağıran bir derleme yapar , ancak o noktadan sonra için talimat infloop() { while(1); }
vermeyi puts
durdurur main
. Dediğim gibi, yürütme sadece fonksiyonun sonundan, sonraki herhangi bir fonksiyona düşer (ancak yığın, işlev girişi için yanlış hizalanmış olduğundan, geçerli bir kuyruk araması bile değildir).
Geçerli seçenekler
label: jmp label
sonsuz döngü yayar
- veya daha sonra 2 dize yazdırmak için başka bir çağrı yayan ve (biz sonsuz döngü kaldırılabilir kabul varsa)
return 0
den main
.
Farkedilmediğim UB olmadığı sürece, "ulaşılamıyor" yazdırılmadan çökme veya başka bir şekilde devam etme, bir C11 uygulaması için açıkça uygun değildir.
Dipnot 1:
Kayıt için, C11'in boş ifadeli sonsuz döngüler için sonlandırma varsayımına izin vermediğine dair kanıt standardını belirten @ Lundin'in cevabı ile aynı fikirdeyim (G / Ç, uçucu, senkronizasyon veya diğer görünür yan etkiler).
Bu, bir döngünün normal bir CPU için boş bir asm döngüsüne derlenmesine izin verecek koşullar kümesidir . (Gövde kaynakta boş olmasa bile, değişkenler için atamalar döngü çalışırken veri yarışı UB'si olmayan diğer iş parçacıkları veya sinyal işleyicileri tarafından görülemez. Bu nedenle, uygun bir uygulama isterse bu döngü gövdelerini kaldırabilir Bu, halkanın kendisinin kaldırılıp kaldırılamayacağı sorusunu bırakır. ISO C11 açıkça hayır diyor.)
C11'in, uygulamanın döngünün sona erdiğini varsayamayacağı (ve bunun UB olmadığı) bir durum olduğu göz önüne alındığında, döngünün çalışma zamanında mevcut olmasını amaçladıkları açık görünüyor. Sonlu zamanda sonsuz miktarda iş yapamayan bir yürütme modeline sahip CPU'ları hedefleyen bir uygulamanın, boş bir sabit sonsuz döngüyü kaldırmak için bir gerekçesi yoktur. Ya da genel olarak bile, kesin ifadeler "sonlandıkları varsayılıp varsayılmayacakları" ile ilgilidir. Bir döngü sonlanamazsa, matematik ve sonsuzluklarla ilgili hangi argümanları yaptığınız ve bazı varsayımsal makinelerde sonsuz miktarda iş yapmanın ne kadar sürdüğü önemli değil, daha sonra kodlara erişilemez demektir .
Bunun yanı sıra, Clang sadece ISO C uyumlu bir DeathStation 9000 değil, çekirdekler ve gömülü nesneler de dahil olmak üzere gerçek dünyadaki düşük seviyeli sistem programlaması için faydalı olması amaçlanmıştır. Dolayısıyla , C11'in kaldırılmasına izin veren argümanları kabul edip while(1);
etmediğiniz, Clang'ın bunu gerçekten yapmak isteyeceği mantıklı değildir. Eğer yazarsanız while(1);
, bu muhtemelen bir kaza değildi. Kaza ile sonsuz olan döngülerin kaldırılması (çalışma zamanı değişken kontrol ifadeleriyle) yararlı olabilir ve derleyicilerin bunu yapması mantıklıdır.
Bir sonraki kesintiye kadar sadece döndürmek istersiniz, ancak C'de yazarsanız kesinlikle olmasını beklediğiniz şey budur. (Ya gelmez , GCC ve Clang meydana sonsuz döngü bir sarıcı işlevi içinde olduğunda Clang için hariç).
Örneğin, ilkel bir OS çekirdeğinde, zamanlayıcının çalışacak hiçbir görevi olmadığında, boşta kalma görevini çalıştırabilir. Bunun ilk uygulaması olabilir while(1);
.
Veya herhangi bir güç tasarruflu rölanti özelliği olmayan donanımlar için tek uygulama bu olabilir. (2000'lerin başına kadar, bu x86'da nadir olmadığını düşünüyorum. hlt
Talimat mevcut olmasına rağmen , IDK, CPU'lar düşük güçlü boşta durumlara sahip olana kadar anlamlı miktarda güç tasarrufu yaptıysa.)