GCC ile x86'da tamsayı taşması neden sonsuz döngüye neden oluyor?


129

Aşağıdaki kod, GCC'de sonsuz bir döngüye girer:

#include <iostream>
using namespace std;

int main(){
    int i = 0x10000000;

    int c = 0;
    do{
        c++;
        i += i;
        cout << i << endl;
    }while (i > 0);

    cout << c << endl;
    return 0;
}

İşte anlaşma şu: İşaretli tamsayı taşması teknik olarak tanımlanmamış bir davranıştır. Ancak x86 üzerindeki GCC, taşma durumunda saran x86 tamsayı talimatlarını kullanarak tamsayı aritmetiği uygular.

Bu nedenle, tanımlanmamış bir davranış olmasına rağmen, taşmayı tamamlamasını bekliyordum. Ancak durum açıkça bu değil. Yani, ne kaçırdım?

Bunu kullanarak derledim:

~/Desktop$ g++ main.cpp -O2

GCC Çıkışı:

~/Desktop$ ./a.out
536870912
1073741824
-2147483648
0
0
0

... (infinite loop)

Optimizasyonlar devre dışı bırakıldığında, sonsuz döngü yoktur ve çıktı doğrudur. Visual Studio ayrıca bunu doğru bir şekilde derler ve aşağıdaki sonucu verir:

Doğru Çıktı:

~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3

İşte diğer bazı varyasyonlar:

i *= 2;   //  Also fails and goes into infinite loop.
i <<= 1;  //  This seems okay. It does not enter infinite loop.

İşte tüm ilgili sürüm bilgileri:

~/Desktop$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ..

...

Thread model: posix
gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4) 
~/Desktop$ 

Yani soru şu: Bu GCC'de bir hata mı? Veya GCC'nin tamsayı aritmetiğini nasıl işlediğiyle ilgili bir şeyi yanlış mı anladım?

* Bu C'yi de etiketliyorum, çünkü bu hatanın C'de çoğalacağını varsayıyorum (henüz doğrulamadım.)

DÜZENLE:

Döngünün montajı şu şekildedir: (eğer doğru bir şekilde tanıyorsam)

.L5:
addl    %ebp, %ebp
movl    $_ZSt4cout, %edi
movl    %ebp, %esi
.cfi_offset 3, -40
call    _ZNSolsEi
movq    %rax, %rbx
movq    (%rax), %rax
movq    -24(%rax), %rax
movq    240(%rbx,%rax), %r13
testq   %r13, %r13
je  .L10
cmpb    $0, 56(%r13)
je  .L3
movzbl  67(%r13), %eax
.L4:
movsbl  %al, %esi
movq    %rbx, %rdi
addl    $1, %r12d
call    _ZNSo3putEc
movq    %rax, %rdi
call    _ZNSo5flushEv
cmpl    $3, %r12d
jne .L5

10
Oluşturulan derleme kodunu dahil ederseniz bu çok daha cevaplanabilir olacaktır gcc -S.
Greg Hewgill

Montaj şaşırtıcı derecede uzun. Yine de içinde düzenlemeli miyim?
Mysticial

Döngünüzle ilgili parçalar lütfen.
Greg Hewgill

12
-1. bunun kesinlikle tanımlanmamış bir davranış olduğunu söylüyorsunuz ve bunun tanımsız bir davranış olup olmadığını soruyorsunuz. bu yüzden bu benim için gerçek bir soru değil.
Johannes Schaub -

8
@ JohannesSchaub-litb Yorum yaptığınız için teşekkürler. Muhtemelen bence kötü sözler. Geri alınmayan oyunuzu kazanmanızı sağlayacak şekilde netleştirmek için elimden geleni yapacağım (ve soruyu buna göre düzenleyeceğim). Temel olarak, UB olduğunu biliyorum. Ancak x86 üzerindeki GCC'nin, taşma durumunda saran x86 tamsayı talimatlarını kullandığını da biliyorum. Bu nedenle, beklenen bu UB olmasına rağmen sarmak için. Ancak olmadı ve bu beni şaşırttı. Dolayısıyla soru.
Gizemli

Yanıtlar:


178

Standart tanımsız bir davranış olduğunu söylediğinde, bu demektir . Her şey olabilir. "Herhangi bir şey", "genellikle tam sayıların etrafını saran tam sayıları içerir, ancak bazen garip şeyler olur".

Evet, x86 CPU'larda tamsayılar genellikle beklediğiniz gibi kaydırılır. Bu, bu istisnalardan biridir. Derleyici, tanımsız davranışa neden olmayacağınızı varsayar ve döngü testini optimize eder. Eğer gerçekten wraparound istiyorsanız, pas -fwrapvetmek g++veya gcczaman derleme; bu size iyi tanımlanmış (iki-tamamlayıcı) taşma semantiği verir, ancak performansa zarar verebilir.


24
Vay canına. Farkında değildim -fwrapv. Bunu işaret ettiğiniz için teşekkürler.
Mysticial

1
Yanlışlıkla sonsuz döngüleri fark etmeye çalışan bir uyarı seçeneği var mı?
Jeff Burdges

5
Burada bahsedilen -Wunsafe-döngü optimizasyonlarını buldum: stackoverflow.com/questions/2982507/…
Jeff Burdges

1
-1 "Evet, x86 CPU'larda tamsayılar genellikle beklediğiniz gibi kaydırılır." bu yanlış. ama ince. hatırladığım kadarıyla onları taşma tuzağına düşürmek mümkün, ama burada bahsettiğimiz konu bu değil ve bunun yapıldığını hiç görmedim. bunun dışında ve x86 bcd işlemlerini göz ardı ederek (C ++ 'da gösterilmesine izin verilmez) x86 tamsayı işlemleri her zaman sarılır, çünkü ikisinin tamamlayıcısıdır. x86 tamsayı işlemlerinin bir özelliği için g ++ hatalı (veya son derece pratik ve saçma) optimizasyonunu yanlış yapıyorsunuz.
Şerefe ve hth. - Alf

5
@ Cheersandhth.-Alf, 'x86 CPU'larda' ile 'bir C derleyicisi kullanarak x86 CPU'lar için geliştirirken' demek istiyorum. Gerçekten hecelememe gerek var mı? Açıkçası, derleyiciler ve GCC hakkındaki tüm konuşmalarım, eğer assembler'da geliştiriyorsanız ilgisizdir, bu durumda tamsayı taşması için anlambilim gerçekten çok iyi tanımlanmıştır.
bdonlan

18

Çok basit: Tanımlanmamış davranış - özellikle optimizasyon ( -O2) açıkken - her şeyin olabileceği anlamına gelir .

Kodunuz, -O2anahtar olmadan beklediğiniz gibi davranır .

Bu arada icl ve tcc ile gayet iyi çalışıyor, ancak böyle şeylere güvenemezsiniz ...

Göre bu , gcc optimizasyonu aslında taşma tamsayı imzalanmış patlatır. Bu, "hatanın" tasarım gereği olduğu anlamına gelir.


Bir derleyicinin tanımsız davranışlar için her şeyin sonsuz döngüsünü tercih etmesi berbattır.
Ters

27
@ Ters: Katılmıyorum. Tanımlanmamış davranışa sahip bir şeyi kodladıysanız, sonsuz bir döngü için dua edin. Tespit etmeyi kolaylaştırır ...
Dennis

Demek istediğim, derleyici aktif olarak UB arıyorsa, bozuk kodu hiper-optimize etmeye çalışmak yerine neden bir istisna eklemeyesiniz?
ters

15
@Inverse: Derleyici aktif olarak tanımlanmamış bir davranış aramıyor, gerçekleşmediğini varsayıyor. Bu, derleyicinin kodu optimize etmesine izin verir. Örneğin, herhangi bir işaretli taşma meydana gelmezse , hesaplama yerine for (j = i; j < i + 10; ++j) ++k;sadece ayarlanır k = 10, çünkü bu her zaman doğru olacaktır.
Dennis

@Inverse Derleyici hiçbir şeyi "seçmedi". Döngüyü kodunuza yazdınız. Derleyici onu icat etmedi.
Orbit'te Hafiflik Yarışları

13

Burada dikkat edilmesi gereken önemli nokta, C ++ programlarının C ++ soyut makinesi için yazılmasıdır (genellikle donanım talimatları aracılığıyla taklit edilir). X86 için derlemekte olmanız, bunun tanımsız bir davranışa sahip olması gerçeğiyle tamamen alakasızdır.

Derleyici, optimizasyonlarını iyileştirmek için tanımlanmamış davranışın varlığını kullanmakta özgürdür (bu örnekte olduğu gibi bir döngüden bir koşulu kaldırarak). Makine kodunun çalıştırıldığında C ++ soyut makine tarafından talep edilen sonucu üretmesi gerekliliği dışında, C ++ seviye yapıları ile x86 seviye makine kodu yapıları arasında garantili veya hatta faydalı bir eşleştirme yoktur.



3

Lütfen millet, tanımsız davranış tam olarak budur, tanımsız . Bu, her şeyin olabileceği anlamına gelir. Pratikte (bu örnekte olduğu gibi), derleyici bunu yapmayacağını varsaymakta özgürdür.çağrılabilir ve eğer bu kodu daha hızlı / daha küçük hale getirebilirse ne isterse yapın. Çalışmaması gereken kodla ne olacağı kimsenin tahmin etmediği bir şeydir. Çevreleyen koda bağlı olacaktır (buna bağlı olarak, derleyici farklı kod üretebilir), kullanılan değişkenler / sabitler, derleyici bayrakları, ... Oh, ve derleyici güncellenebilir ve aynı kodu farklı bir şekilde yazabilir veya kod oluşturma konusunda farklı bir görüşe sahip başka bir derleyici edinin. Ya da sadece farklı bir makine alın, aynı mimari çizgisindeki başka bir model bile kendi tanımlanmamış davranışına sahip olabilir (tanımlanmamış işlem kodlarına bakın, bazı girişimci programcılar bu eski makinelerin bazılarında bazen yararlı şeyler yaptığını keşfetti ...) . Orada hiçbir"derleyici tanımlanmamış davranışlarda kesin bir davranış sergiliyor". Uygulama tanımlı alanlar vardır ve orada derleyicinin tutarlı davranacağına güvenebilmelisiniz.


1
Evet, tanımlanmamış davranışın ne olduğunu çok iyi biliyorum. Ancak, belirli bir ortam için dilin belirli yönlerinin nasıl uygulandığını bildiğinizde, bazı UB türlerini görmeyi bekleyebilirsiniz, diğerlerini görmeyi bekleyebilirsiniz. GCC'nin tamsayı aritmetiğini x86 tamsayı aritmetiği olarak uyguladığını biliyorum - bu da taşmayı sarıyor. Ben de bu şekilde davranışı varsaydım. Beklemediğim şey, GCC'nin bdonlan'ın yanıtladığı gibi başka bir şey yapmasıydı.
Gizemli

7
Yanlış. Olan şey, GCC'nin tanımsız davranışı başlatmayacağınızı varsaymasına izin verilmesidir, bu nedenle, kodu gerçekleşemeyecekmiş gibi yayar . O takdirde yok olur, talimatlar ne ile sormak yapmak hiçbir idam tanımsız davranış ve sonuç CPU yapar odur. Yani, x86'da x86 işi yapar. Başka bir işlemci ise, tamamen farklı bir şey yapabilir. Veya derleyici, tanımsız davranışa çağırdığınızı ve nethack'i başlattığınızı anlayacak kadar akıllı olabilir (evet, gcc'nin bazı eski sürümleri tam olarak bunu yaptı).
vonbrand

4
Yorumumu yanlış okuduğuna inanıyorum. "Beklemediğim şey" dedim - bu yüzden soruyu ilk başta sordum. Ben vermedi GCC herhangi bir hile çekmeye bekliyoruz.
Gizemli

1

Bir derleyici, tamsayı taşmasının "kritik olmayan" Tanımlanmamış Davranış biçimi olarak kabul edilmesi gerektiğini belirtecek olsa bile (Ek L'de tanımlandığı gibi), bir tamsayı taşmasının sonucu, daha spesifik bir davranış için belirli bir platform vaadi olmadığında, en azından "kısmen belirsiz bir değer" olarak kabul edilir. Bu tür kurallar altında, 1073741824 + 1073741824'ün eklenmesi keyfi olarak 2147483648 veya -2147483648 veya 2147483648 mod 4294967296 ile uyumlu başka bir değer olarak kabul edilebilir ve eklemelerle elde edilen değerler keyfi olarak 0 mod 4294967296'ya uygun herhangi bir değer olarak kabul edilebilir.

Taşmanın "kısmen belirsiz değerler" vermesine izin veren kurallar, Ek L'nin lafzına ve ruhuna uymak için yeterince iyi tanımlanmış olacaktır, ancak bir derleyicinin, sınırlandırılmamış taşmalar olması halinde gerekçelendirilebilecek aynı genel olarak yararlı çıkarımları yapmasını engellemeyecektir. Tanımsız Davranış. Bir derleyicinin, birçok durumda birincil etkisi programcıların tek amacı bu tür "optimizasyonları" önlemek olan koda fazladan dağınıklık eklemesini gerektiren bazı sahte "optimizasyonlar" yapmasını engelleyecektir; bunun iyi bir şey olup olmayacağı kişinin bakış açısına bağlıdır.

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.