Bu döngü neden “uyarı: yineleme 3u tanımsız davranış başlatır” üretir ve 4 satırdan fazlasını üretir?


162

Bunu derleme:

#include <iostream>

int main()
{
    for (int i = 0; i < 4; ++i)
        std::cout << i*1000000000 << std::endl;
}

ve gccaşağıdaki uyarıyı verir:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

İmzalı bir tamsayı taşması olduğunu anlıyorum.

Ne elde edemiyorum neden ideğer bu taşma işlemi tarafından kırık olduğunu?

I cevapları okudum Neden sonsuz bir döngüye neden GCC ile x86 üzerinde taşma tamsayı yapar? , ancak bunun neden olduğu konusunda hala net değilim - "tanımsız" ın "her şey olabilir" anlamına geldiğini anlıyorum, ancak bu özel davranışın altında yatan neden nedir?

Çevrimiçi: http://ideone.com/dMrRKR

Derleyici: gcc (4.8)


49
İmzalı tamsayı taşması => Tanımsız Davranış => Burun Daemonları. Ama itiraf etmeliyim ki, bu örnek oldukça hoş.
dyp

1
Montaj çıktısı: goo.gl/TtPmZn
Bryan Chen

1
GCC 4.8'de O2, ve O3bayrağıyla olur, ancak değil O0veya olurO1
Alex

3
@dyp Nazal Daemons okuduğumda komik bir şey gördüğünüzde burnunuzu hafifçe solumaktan oluşan "imgur kahkahası" yaptım. Sonra fark ettim ... Bir Nazal Daemon tarafından lanetlenmeliyim!
corsiKa

4
Birisi "teknik olarak UB ama bir şey yapmalı" bir dahaki sefere bağlayabilirsiniz böylece bu yer imi :)
MM

Yanıtlar:


107

İmzalı tamsayı taşması (açık konuşmak gerekirse, "imzasız tamsayı taşması" diye bir şey yoktur) tanımsız davranış anlamına gelir . Ve bu her şeyin olabileceği anlamına gelir ve bunun neden C ++ kuralları altında gerçekleştiğini tartışmak mantıklı değildir.

C ++ 11 taslak N3337: §5.4: 1

Bir ifadenin değerlendirilmesi sırasında, sonuç matematiksel olarak tanımlanmamışsa veya türü için temsil edilebilir değerler aralığında değilse, davranış tanımlanmamıştır. [Not: mevcut C ++ uygulamalarının çoğu tamsayılar üzerinde tamsayıyı yok sayar. Bölmenin sıfıra muamele edilmesi, sıfır bölen kullanılarak bir kalanın oluşturulması ve tüm kaplama noktası istisnaları makineler arasında değişiklik gösterir ve genellikle bir kütüphane işlevi ile ayarlanabilir. —End not]

Kodunuz derlendi g++ -O3uyarı veriyor (olmadan bile -Wall)

a.cpp: In function 'int main()':
a.cpp:11:18: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^
a.cpp:9:2: note: containing loop
  for (int i = 0; i < 4; ++i)
  ^

Programın ne yaptığını analiz etmenin tek yolu, oluşturulan derleme kodunu okumaktır.

İşte tam montaj listesi:

    .file   "a.cpp"
    .section    .text$_ZNKSt5ctypeIcE8do_widenEc,"x"
    .linkonce discard
    .align 2
LCOLDB0:
LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  __ZNKSt5ctypeIcE8do_widenEc
    .def    __ZNKSt5ctypeIcE8do_widenEc;    .scl    2;  .type   32; .endef
__ZNKSt5ctypeIcE8do_widenEc:
LFB860:
    .cfi_startproc
    movzbl  4(%esp), %eax
    ret $4
    .cfi_endproc
LFE860:
LCOLDE0:
LHOTE0:
    .section    .text.unlikely,"x"
LCOLDB1:
    .text
LHOTB1:
    .p2align 4,,15
    .def    ___tcf_0;   .scl    3;  .type   32; .endef
___tcf_0:
LFB1091:
    .cfi_startproc
    movl    $__ZStL8__ioinit, %ecx
    jmp __ZNSt8ios_base4InitD1Ev
    .cfi_endproc
LFE1091:
    .section    .text.unlikely,"x"
LCOLDE1:
    .text
LHOTE1:
    .def    ___main;    .scl    2;  .type   32; .endef
    .section    .text.unlikely,"x"
LCOLDB2:
    .section    .text.startup,"x"
LHOTB2:
    .p2align 4,,15
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB1084:
    .cfi_startproc
    leal    4(%esp), %ecx
    .cfi_def_cfa 1, 0
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    .cfi_escape 0x10,0x5,0x2,0x75,0
    movl    %esp, %ebp
    pushl   %edi
    pushl   %esi
    pushl   %ebx
    pushl   %ecx
    .cfi_escape 0xf,0x3,0x75,0x70,0x6
    .cfi_escape 0x10,0x7,0x2,0x75,0x7c
    .cfi_escape 0x10,0x6,0x2,0x75,0x78
    .cfi_escape 0x10,0x3,0x2,0x75,0x74
    xorl    %edi, %edi
    subl    $24, %esp
    call    ___main
L4:
    movl    %edi, (%esp)
    movl    $__ZSt4cout, %ecx
    call    __ZNSolsEi
    movl    %eax, %esi
    movl    (%eax), %eax
    subl    $4, %esp
    movl    -12(%eax), %eax
    movl    124(%esi,%eax), %ebx
    testl   %ebx, %ebx
    je  L15
    cmpb    $0, 28(%ebx)
    je  L5
    movsbl  39(%ebx), %eax
L6:
    movl    %esi, %ecx
    movl    %eax, (%esp)
    addl    $1000000000, %edi
    call    __ZNSo3putEc
    subl    $4, %esp
    movl    %eax, %ecx
    call    __ZNSo5flushEv
    jmp L4
    .p2align 4,,10
L5:
    movl    %ebx, %ecx
    call    __ZNKSt5ctypeIcE13_M_widen_initEv
    movl    (%ebx), %eax
    movl    24(%eax), %edx
    movl    $10, %eax
    cmpl    $__ZNKSt5ctypeIcE8do_widenEc, %edx
    je  L6
    movl    $10, (%esp)
    movl    %ebx, %ecx
    call    *%edx
    movsbl  %al, %eax
    pushl   %edx
    jmp L6
L15:
    call    __ZSt16__throw_bad_castv
    .cfi_endproc
LFE1084:
    .section    .text.unlikely,"x"
LCOLDE2:
    .section    .text.startup,"x"
LHOTE2:
    .section    .text.unlikely,"x"
LCOLDB3:
    .section    .text.startup,"x"
LHOTB3:
    .p2align 4,,15
    .def    __GLOBAL__sub_I_main;   .scl    3;  .type   32; .endef
__GLOBAL__sub_I_main:
LFB1092:
    .cfi_startproc
    subl    $28, %esp
    .cfi_def_cfa_offset 32
    movl    $__ZStL8__ioinit, %ecx
    call    __ZNSt8ios_base4InitC1Ev
    movl    $___tcf_0, (%esp)
    call    _atexit
    addl    $28, %esp
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc
LFE1092:
    .section    .text.unlikely,"x"
LCOLDE3:
    .section    .text.startup,"x"
LHOTE3:
    .section    .ctors,"w"
    .align 4
    .long   __GLOBAL__sub_I_main
.lcomm __ZStL8__ioinit,1,1
    .ident  "GCC: (i686-posix-dwarf-rev1, Built by MinGW-W64 project) 4.9.0"
    .def    __ZNSt8ios_base4InitD1Ev;   .scl    2;  .type   32; .endef
    .def    __ZNSolsEi; .scl    2;  .type   32; .endef
    .def    __ZNSo3putEc;   .scl    2;  .type   32; .endef
    .def    __ZNSo5flushEv; .scl    2;  .type   32; .endef
    .def    __ZNKSt5ctypeIcE13_M_widen_initEv;  .scl    2;  .type   32; .endef
    .def    __ZSt16__throw_bad_castv;   .scl    2;  .type   32; .endef
    .def    __ZNSt8ios_base4InitC1Ev;   .scl    2;  .type   32; .endef
    .def    _atexit;    .scl    2;  .type   32; .endef

Meclisi bile okuyamıyorum, hatta hatta görebiliyorum addl $1000000000, %edi. Ortaya çıkan kod daha çok benziyor

for(int i = 0; /* nothing, that is - infinite loop */; i += 1000000000)
    std::cout << i << std::endl;

@TC'nin bu yorumu:

Bunun gibi bir şey olduğundan şüpheleniyorum: (1) i2'den büyük herhangi bir değere sahip her yineleme tanımlanmamış davranışa sahip olduğundan -> (2) i <= 2optimizasyon amacıyla -> (3) döngü koşulunun her zaman doğru olduğunu varsayabiliriz -> (4 ) sonsuz bir döngüye dönüştürülür.

OP kodunun derleme kodunu, tanımlanmamış bir davranış olmadan aşağıdaki kodun derleme koduyla karşılaştırmamı sağladı.

#include <iostream>

int main()
{
    // changed the termination condition
    for (int i = 0; i < 3; ++i)
        std::cout << i*1000000000 << std::endl;
}

Ve aslında, doğru kodun fesih koşulu vardır.

    ; ...snip...
L6:
    mov ecx, edi
    mov DWORD PTR [esp], eax
    add esi, 1000000000
    call    __ZNSo3putEc
    sub esp, 4
    mov ecx, eax
    call    __ZNSo5flushEv
    cmp esi, -1294967296 // here it is
    jne L7
    lea esp, [ebp-16]
    xor eax, eax
    pop ecx
    ; ...snip...

Aman Tanrım, bu tamamen belli değil! Adil değil! Ateşle yargılanmak istiyorum!

Bununla başa çık, buggy kodunu yazdın ve kendini kötü hissetmelisin. Sonuçları doğurun.

... veya alternatif olarak, daha iyi tanılama ve daha iyi hata ayıklama araçlarından uygun şekilde faydalanın.

  • tüm uyarıları etkinleştir

    • -Wallyanlış pozitif olmayan tüm yararlı uyarıları etkinleştiren gcc seçeneğidir. Bu her zaman kullanmanız gereken çıplak bir minimumdur.
    • gcc'nin başka birçok uyarı seçeneği vardır , ancak -Wallyanlış pozitifler konusunda uyarı verebildikleri için etkinleştirilmezler.
    • Visual C ++ ne yazık ki yararlı uyarılar verme yeteneği ile geride kalıyor. En azından IDE bazılarını varsayılan olarak etkinleştirir.
  • hata ayıklama için hata ayıklama bayraklarını kullan

    • tamsayı taşması -ftrapviçin programı taşma sırasında yakalar,
    • Clang derleyici bunun için mükemmel: -fcatch-undefined-behaviortanımsız davranış örneklerinin bir sürü yakalar (not: "a lot of" != "all of them")

Benim tarafımdan yazılmamış ve yarın gönderilmesi gereken bir programda spagetti karışıklığı var! YARDIM !!!!!! 111oneone

Gcc kullanın -fwrapv

Bu seçenek derleyiciye imzalı aritmetik toplama, çıkarma ve çarpma taşmasının ikişer tamamlayıcı gösterim kullanarak sarıldığını varsaymasını ister.

1 - §3.9.1.4'te belirtildiği gibi, bu kural "işaretsiz tam sayı taşması" için geçerli değildir

İmzasız olarak adlandırılan işaretsiz tamsayılar aritmetik modulo 2 n yasalarına uyacaktır ; burada n, belirli bir tamsayı boyutunun değer gösterimindeki bit sayısıdır.

ve örneğin sonucu UINT_MAX + 1matematiksel olarak tanımlanır - aritmetik modulo 2 n kuralları ile


7
Burada neler olduğunu hala tam olarak anlamıyorum ... Neden ikendisi etkileniyor? Genel olarak tanımlanmamış davranış bu tür garip yan etkilere sahip değildir, sonuçta bir değer i*100000000olmalıdır
vsoftco

26
Bunun gibi bir şey olduğundan şüpheleniyorum: (1) i2'den büyük herhangi bir değere sahip her yineleme tanımlanmamış davranışa sahip olduğundan -> (2) i <= 2optimizasyon amacıyla -> (3) döngü koşulunun her zaman doğru olduğunu varsayabiliriz -> (4 ) sonsuz bir döngüye dönüştürülür.
TC

28
@vsoftco: Olan şey , indüksiyon değişkeni eliminasyonu gibi bir güç azalması durumudur . Derleyici, bunun yerine her yinelemeyi 1e9 oranında artıran (ve döngü koşulunu buna göre değiştirerek) kod yayarak çarpmayı ortadan kaldırır . Bu, "sanki" kuralı altında mükemmel bir şekilde geçerli bir optimizasyondur, çünkü bu program iyi davrandığında farkı gözlemleyememiştir. Ne yazık ki, değil ve optimizasyon "sızdırıyor". i
JohannesD

8
@JohannesD bunun kırılmasının nedenini çiviler. Ancak, döngü sonlandırma koşulu taşma içermediğinden bu kötü bir optimizasyon. Güç azaltma kullanımı iyiydi - İşlemcideki çarpanın (4 * 100000000), (100000000 + 100000000 + 100000000 + 100000000) ile farklı olacak ne yapacağını bilmiyorum ve "bu tanımsız - kim bilir "mantıklı. Ancak, 4 kez çalışan ve tanımlanmamış sonuçlar üreten, iyi davranılmış bir döngü olması gereken şeyin yerine, "tanımsız!" aptallıktır.
Julie in Austin

14
@JulieinAustin Senin için aptalca olsa da, tamamen yasal. Olumlu tarafı, derleyici sizi bu konuda uyarır.
milleniumbug

68

Kısa cevap, gccözellikle bu sorunu belgelemiştir, gcc 4.8 sürüm notlarında şunu söyleyebiliriz ( ileriye dönük maden ):

GCC artık dil standartlarının getirdiği kısıtlamaları kullanarak döngülerin yineleme sayısı için bir üst sınır türetmek için daha agresif bir analiz kullanmaktadır . Bu, SPEC CPU 2006 464.h264ref ve 416.gamess gibi uygun olmayan programların beklendiği gibi çalışmamasına neden olabilir. Bu agresif analizi devre dışı bırakmak için yeni bir seçenek olan -fno-aggressive-loop-optimizations eklendi. Sabit sayıda yinelemeye sahip olan ancak son yinelemeye ulaşmadan önce veya son yinelemede döngüde tanımlanmamış davranışın oluştuğu bilinen bazı döngülerde GCC, yinelemelerin sayısının alt üst sınırını türetmek yerine döngüdeki tanımlanmamış davranış hakkında uyarır. döngü için. Uyarı, -Wno-agresif-döngü optimizasyonlarıyla devre dışı bırakılabilir.

ve eğer -fno-aggressive-loop-optimizationssonsuz döngü davranışını kullanırsak , test etmem gereken tüm durumlarda bu durum durmalıdır.

Uzun cevap, imzalı tamsayı taşmasının taslak C ++ standart bölümü 5 İfadeler paragraf 4'e bakarak tanımlanmamış bir davranış olduğunu bilmekle başlar :

Bir ifadenin değerlendirilmesi sırasında sonuç, matematiksel olarak tanımlanmazsa veya türü için temsil edilebilir değerler aralığında değilse, davranış tanımsızdır . [Not: mevcut C ++ uygulamalarının çoğu tamsayı taşmalarını yoksayar. Bölmenin sıfır olarak işlenmesi, sıfır bölen kullanılarak bir kalanın oluşturulması ve tüm kayan nokta istisnaları makineler arasında değişiklik gösterir ve genellikle bir kütüphane işlevi tarafından ayarlanabilir. —End not

Standardın, tanımlanmamış davranışın, şu tanımla gelen nottan tahmin edilemez olduğunu söylediğini biliyoruz:

[Not: Bu Uluslararası Standart herhangi bir açık davranış tanımını atladığında veya bir program hatalı bir yapı veya hatalı veri kullandığında tanımlanmamış davranış beklenebilir. İzin verilen tanımlanamayan davranış durumu öngörülemeyen sonuçlarla tamamen görmezden gelmekten, çeviri veya programın yürütülmesi sırasında ortamın karakteristiği olan (tanı mesajı verilmiş veya verilmeden) belgelenmiş bir şekilde davranmaya, bir çeviriyi veya yürütmeyi sonlandırmaya kadar değişir. bir teşhis mesajı). Birçok hatalı program yapısı tanımlanmamış davranış oluşturmaz; teşhis edilmeleri gerekir. —End not]

Ancak gccoptimizer bunu sonsuz bir döngüye dönüştürmek için dünyada ne yapabilir ? Tamamen tuhaf geliyor. Ama neyse ki gccbize uyarıda bulmamız için bir ipucu veriyor:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

İpucu, bu Waggressive-loop-optimizationsne anlama geliyor? Neyse ki bizim için bu optimizasyon bu şekilde kodu ilk kez kırmadı ve şanslıyız çünkü John Regehr , aşağıdaki kodu gösteren GCC 4.8 öncesi Kırık SPEC 2006 Deneyleri makalesinde bir vakayı belgeledi :

int d[16];

int SATD (void)
{
  int satd = 0, dd, k;
  for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
  }
  return satd;
}

makale şöyle diyor:

Tanımlanmamış davranış, döngüden çıkmadan hemen önce d [16] 'ya erişiyor. C99'da, dizinin sonundan bir konum ötedeki bir öğeye işaretçi oluşturmak yasaldır, ancak bu işaretçinin kaydı kaldırılmamalıdır.

ve daha sonra diyor ki:

Ayrıntılı olarak, olan biten. AC derleyicisinin, d [++ k] görünmesi üzerine, artan k değerinin dizi sınırları içinde olduğunu varsaymasına izin verilir, aksi takdirde tanımsız davranış oluşur. Buradaki kod için GCC, k'nin 0..15 aralığında olduğunu çıkarabilir. Biraz sonra, GCC k <16'yı gördüğünde, kendisine şöyle diyor: “Aha- bu ifade her zaman doğrudur, bu yüzden sonsuz bir döngüye sahibiz.” Derleyicinin yararlı bir veri akışı olgusunu çıkarmak için iyi tanımlanmışlık varsayımını kullandığı durum,

Derleyicinin bazı durumlarda yapması gereken şey, imzalı tamsayı taşması tanımsız davranış olduğu için varsayılır, o izaman her zaman daha az olmalıdır 4ve bu nedenle sonsuz bir döngüye sahibiz.

Bunun, bu kodu gördüğünüz yerde rezil Linux çekirdeği boş gösterici denetiminin kaldırılmasına çok benzediğini açıklıyor :

struct foo *s = ...;
int x = s->f;
if (!s) return ERROR;

gccberi anlaşılmaktadır siçinde deferenced edildi s->f;ve o zamandan beri boş gösterici dereferencing sonra tanımsız davranıştır snull olmamalı ve bu nedenle uzak optimize if (!s)sonraki satırda çek.

Buradaki ders, modern optimize edicilerin tanımsız davranışlardan yararlanma konusunda çok agresif olmaları ve büyük olasılıkla daha agresif olmalarıdır. Açıkçası, birkaç örnekle, optimize edicinin bir programcı için tamamen mantıksız görünen şeyleri yaptığını görebiliriz, ancak optimize ediciler perspektifinden geriye bakıldığında mantıklı.


7
Derleyici yazarın yaptığı şeyin bu olduğunu anlıyorum (derleyiciler ve hatta bir optimizatör yazıyordum), ancak "tanımlanmamış" olsalar bile "yararlı" davranışlar var ve bu daha agresif optimizasyona doğru ilerliyor sadece delilik. Yukarıda alıntıladığınız yapı yanlıştır, ancak hata kontrolünü optimize etmek kullanıcı dostudur.
Julie in Austin

1
@JulieinAustin Geliştiricilerin tanımsız davranışlardan kaçınmaları gerektiğini söyleyen bu oldukça şaşırtıcı bir davranış. Açıkçası, derleyicinin geliştiriciye de daha iyi geri bildirim sağlaması gerekiyor. Bu durumda, yeterince bilgilendirici olmasa da bir uyarı verilir.
Shafik Yaghmour

3
Bence bu iyi bir şey, daha iyi, daha hızlı kod istiyorum. UB asla kullanılmamalıdır.
paulm

1
@paulm ahlaki olarak UB açıkça kötüdür, ancak geliştiricilerin üretim uygulamalarını etkilemeden önce UB'yi ve diğer sorunları yakalamasına yardımcı olmak için clang statik analizörü gibi daha iyi araçlar sağlamakla tartışmak zordur .
Shafik Yaghmour

1
@ShafikYaghmour Ayrıca, geliştiriciniz uyarıları görmezden geliyorsa, clang çıktısına dikkat etme olasılıkları nedir? Bu sorun, agresif bir "haksız uyarı yok" politikası ile kolayca yakalanabilir. Clang tavsiye edilir, ancak gerekli değildir.
deworde

24

tl; dr Kod, tamsayı + pozitif tamsayı == negatif tamsayı olan bir sınama oluşturur . Genellikle optimizer bunu optimize etmez, ancak bir std::endlsonraki kullanımda derleyici bu testi optimize eder. endlHenüz neyin özel olduğunu bulamadım .


-O1 ve daha yüksek seviyelerdeki montaj kodundan, gcc'nin döngüyü şu şekilde yeniden düzenlediği açıktır:

i = 0;
do {
    cout << i << endl;
    i += NUMBER;
} 
while (i != NUMBER * 4)

Düzgün çalışan en büyük değer 715827882, yani floor ( INT_MAX/3) değeridir . Montaj snippet'i -O1:

L4:
movsbl  %al, %eax
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
addl    $715827882, %esi
cmpl    $-1431655768, %esi
jne L6
    // fallthrough to "return" code

Not -1431655768olduğu 4 * 7158278822'nin belirtilen bütünleyici.

Vurmak -O2bunu aşağıdakilere göre optimize eder:

L4:
movsbl  %al, %eax
addl    $715827882, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655768, %esi
jne L6
leal    -8(%ebp), %esp
jne L6 
   // fallthrough to "return" code

Dolayısıyla, yapılan optimizasyon yalnızca addldaha yüksek bir seviyeye taşınmasıdır.

715827883Bunun yerine yeniden derlersek, -O1 sürümü değiştirilen sayı ve test değeri dışında aynı olur. Ancak, -O2 daha sonra bir değişiklik yapar:

L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2

Şurada olduğu cmpl $-1431655764, %esiyerde -O1, bu satır kaldırıldı -O2. İyileştirici ekleyerek karar verdi olmalı 715827883etmek %esieşit asla -1431655764.

Bu oldukça şaşırtıcı. O ekleme INT_MIN+1 gelmez beklenen sonucu üretmek, böylece iyileştirici olduğuna karar verdi olmalı %esiolamaz INT_MIN+1ve bu karar neden emin değilim.

Çalışma örneğinde 715827882, bir sayıya eklemenin eşit olamayacağı sonucuna varmak eşit derecede geçerli görünmektedir INT_MIN + 715827882 - 2! (bu yalnızca sarmalama gerçekleşirse mümkündür), ancak bu örnekteki satırı optimize etmez.


Kullandığım kod:

#include <iostream>
#include <cstdio>

int main()
{
    for (int i = 0; i < 4; ++i)
    {
        //volatile int j = i*715827883;
        volatile int j = i*715827882;
        printf("%d\n", j);

        std::endl(std::cout);
    }
}

Eğer std::endl(std::cout)o zaman kaldırılır optimizasyon artık oluşur. Aslında, onunla değiştirilmesi , satır içi std::cout.put('\n'); std::flush(std::cout);de olsa optimizasyonun gerçekleşmemesine neden olur std::endl.

Inlining std::endldöngü yapısının önceki bölümünü etkiler gibi görünüyor (ki ne yaptığını tam olarak anlamıyorum ama başka biri yaparsa buraya göndereceğim):

Orijinal kod ve -O2:

L2:
movl    %esi, 28(%esp)
movl    28(%esp), %eax
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    __ZSt4cout, %eax
movl    -12(%eax), %eax
movl    __ZSt4cout+124(%eax), %ebx
testl   %ebx, %ebx
je  L10
cmpb    $0, 28(%ebx)
je  L3
movzbl  39(%ebx), %eax
L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2                  // no test

Ait mymanual inlining ile std::endl, -O2:

L3:
movl    %ebx, 28(%esp)
movl    28(%esp), %eax
addl    $715827883, %ebx
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    $10, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    $__ZSt4cout, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655764, %ebx
jne L3
xorl    %eax, %eax

Bu ikisi arasındaki bir fark %esi, orijinalde ve %ebxikinci versiyonda kullanılmasıdır; genel olarak %esive %ebxarasında tanımlanan anlambilimde bir fark var mı? (X86 montajı hakkında fazla bir şey bilmiyorum).


Optimize edicinin mantığının tam olarak ne olduğunu bulmak iyi olurdu, bu aşamada neden bazı vakaların testin optimize edildiğini ve bazılarının yapmadığını açık değil
MM

8

Gcc'de bildirilen bu hatanın başka bir örneği, sabit sayıda yineleme için yürüten bir döngünüz olduğunda, ancak sayaç değişkenini, bu sayıdan daha az öğe içeren bir diziye dizin olarak kullanmanızdır:

int a[50], x;

for( i=0; i < 1000; i++) x = a[i];

Derleyici, bu döngünün 'a' dizisinin dışındaki belleğe erişmeye çalışacağını belirleyebilir. Derleyici, bu oldukça şifreli mesajla şikayet ediyor:

yineleme xxu tanımlanmamış davranışı başlatır [-Werror = agresif-döngü-optimizasyonları]


Daha da şifreli olan, mesajın yalnızca optimizasyon açıldığında yayılmasıdır. M $ VB "Sınır dışı dizi" aptallar için mi?
Ravi Ganesh

6

Ne elde edemiyorum neden i değer taşma işlemi tarafından kırık?

Görünüşe göre tamsayı taşması 4. yinelemede meydana gelir (for i = 3). signedtamsayı taşması tanımsız davranış başlatır . Bu durumda hiçbir şey tahmin edilemez. Döngü sadece birkaç 4kez yinelenebilir veya sonsuz veya başka bir şeye gidebilir!
Sonuç, derleyiciyi derleyiciye veya hatta aynı derleyicinin farklı sürümleri için değişebilir.

C11: 1.3.24 tanımlanmamış davranış:

bu standardın herhangi bir gereklilik getirmediği
davranış [Not: Bu standardın açık bir davranış tanımını atlaması veya bir program hatalı bir yapı veya hatalı veri kullanması durumunda tanımlanmamış davranış beklenebilir. İzin verilen tanımlanamayan davranış durumu öngörülemeyen sonuçlarla tamamen görmezden gelmekten, çeviri veya programın yürütülmesi sırasında ortamın karakteristiği olan (tanı mesajı verilmiş veya verilmeden) belgelenmiş bir şekilde davranmaya, bir çeviriyi veya yürütmeyi sonlandırmaya kadar değişir. bir teşhis mesajı) . Birçok hatalı program yapısı tanımlanmamış davranış oluşturmaz; teşhis edilmeleri gerekir. —End not]


@bits_international; Evet.
2014'te

4
Haklısın, neden indirdiğimi açıklamak adil. Bu cevaptaki bilgiler doğrudur, ancak eğitici değildir ve odadaki fili tamamen görmezden gelir: kırılma , taşmaya neden olan operasyondan farklı bir yerde (durma koşulu) gerçekleşir. Bu sorunun özü, her ne kadar bu sorunun özü olsa da, açıklanmamıştır. Bu, öğretmenin cevabının sadece sorunun özünü ele almakla kalmayıp, başka soruları da cesaretlendirmediği tipik bir kötü öğretmen durumudur. Neredeyse kulağa benziyor ...
Szabolcs

5
"Bunun tanımsız bir davranış olduğunu görüyorum ve bu noktadan itibaren nasıl ya da neden kırıldığı umurumda değil. Standart kırılmasına izin veriyor. Başka soru yok." Öyle demek istememiş olabilirsiniz, ama öyle görünüyor. Ben SO (bu talihsiz ortak) tutum daha az görmeyi umuyorum. Bu pratik olarak yararlı değildir. Kullanıcı girdisi alırsanız, standart , programın diğer herhangi bir bölümünün bu nedenle patlayabileceğini söylüyor olsa bile, her bir işaretli tamsayı işleminden sonra taşmayı kontrol etmek makul değildir . Anlamak nasıl kırılır yapar pratikte böyle önlenmesine yardımcı problemleri.
Szabolcs

2
@Szabolcs: C'yi iki dil olarak düşünmek en iyisi olabilir, bunlardan biri, basit derleyicilerin amaçlanan hedef platformlarında güvenilir olacak yapılardan yararlanan ancak kullanamayan programcıların yardımıyla verimli bir yürütülebilir kod elde etmesine izin vermek için tasarlanmıştır. ve bu nedenle Standartlar komitesi tarafından göz ardı edildi ve Standartların destek vermediği tüm bu yapıları hariç tutan ikinci bir dil, derleyicilerin programcıların sahip olduklarından daha ağır basabilecek veya ağır basamayacak ek optimizasyonlar uygulamalarına izin vermek için Pes etmek.
supercat

1
@Szabolcs " Eğer kullanıcı girişi alırsanız, imzalı her tamsayı işleminden sonra taşma olup olmadığını kontrol etmek mantıklı değildir " - doğru çünkü bu noktada çok geç. Her imzalı tam sayı işleminden önce taşma olup olmadığını kontrol etmeniz gerekir .
melpomene
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.