Program çevrimiçi IDE'lerde garip davranıyor


82

Aşağıdaki C ++ programına rastladım ( kaynak ):

#include <iostream>
int main()
{
    for (int i = 0; i < 300; i++)
        std::cout << i << " " << i * 12345678 << std::endl;
}

Basit bir program gibi görünüyor ve yerel makinemde doğru çıktıyı veriyor, örneğin:

0 0
1 12345678
2 24691356
...
297 -628300930
298 -615955252
299 -603609574

Ancak, codechef gibi çevrimiçi IDE'lerde aşağıdaki çıktıyı verir:

0 0
1 12345678
2 24691356
...
4167 -95167326
4168 -82821648
4169 -7047597

forDöngü neden 300'de bitmiyor ? Ayrıca bu program her zaman tarihinde sona erer 4169. Neden 4169başka bir değer değil?


45
Muhtemelen işaretli tamsayı taşması tanımsız davranışa sahip olduğu için.
eerorika

17
İmzalı taşmanın UB olduğunu biliyorum ama bu muhteşem bir başarısızlık ...
Galik

45
MM

12
Neden ilk çıktının "doğru çıktı" olduğunu düşündüğünüzü merak ediyorum.
Orbit'te Hafiflik Yarışları

5
@LightnessRacesinOrbit - tamam, bu hatırlatma olduğunu değerli!
davidbak

Yanıtlar:


108

Çevrimiçi derleyicilerin GCC veya uyumlu bir derleyici kullandığını varsayacağım. Elbette, başka herhangi bir derleyicinin de aynı optimizasyonu yapmasına izin verilir, ancak GCC belgeleri ne yaptığını iyi açıklar:

-faggressive-loop-optimizations

Bu seçenek, döngü iyileştiricisine bir döngünün yineleme sayısı için sınırlar türetmek üzere dil kısıtlamalarını kullanmasını söyler. Bu, döngü kodunun, örneğin işaretli tamsayı taşmalarına veya sınır dışı dizi erişimlerine neden olarak tanımlanmamış davranışları başlatmadığını varsayar. Döngünün yineleme sayısının sınırları, döngü açma ve sıyırma ve döngü çıkış testi optimizasyonlarına rehberlik etmek için kullanılır. Bu seçenek varsayılan olarak etkindir.

Bu seçenek yalnızca, UB'nin kanıtlandığı durumlara dayalı olarak varsayımlar yapılmasına izin verir. Bu varsayımlardan yararlanmak için, sabit katlama gibi diğer optimizasyonların etkinleştirilmesi gerekebilir.


İmzalı tamsayı taşması tanımsız davranışa sahiptir. Optimizer, 173'ten ibüyük herhangi bir değerin UB'ye neden olacağını kanıtlayabildi ve UB olmadığını varsayabildiğinden, ibunun asla 173'ten büyük olmadığını da varsayabilir . Daha sonra bunun i < 300her zaman doğru olduğunu kanıtlayabilir ve böylece döngü koşulu optimize edilebilir.

Neden 4169 ve başka bir değer değil?

Bu siteler muhtemelen gösterdikleri ve aynı sınırı paylaştıkları çıktı satırlarının (veya karakterlerin veya baytların) sayısını sınırlar.


3
Derleyici, 173'ten ibüyük herhangi bir değer için UB olacağını kanıtlayabilirse, neden anlamsız bir optimizasyon yapmak yerine bir uyarı yayınlamıyor?
Jabberwocky

7
@MichaelWalz Bir tane yayar.
HolyBlackCat

3
@MichaelWalz: Gereksiz boole testlerini kaldırmak anlamsız bir optimizasyon değildir; çok kullanışlı. Ne olurdu olmak (çoğunlukla) anlamsız devre dışı ek kod ekliyor / kırık kaynak kodu varlığında optimizasyonu geri.

25
@MichaelWalz: Eğer önerdiği gibi (aslında olduğu gibi bazen, olası bir oluşum üzerinde uyarabilir olsa derleyici güvenilir UB algılayamaz yapar burada). Bunun yerine, UB olmayacağı varsayımıyla en iyi niyetle ilerleyebilir . Bunlar belki incelikli ama aslında önemli ölçüde farklı şeylerdir. Derleyici "aha! UB! Şimdi böyle ve böyle bir optimizasyonu açabilirim" demiyor - optimizasyon her zaman oradaydı. Her zaman böyle şeyler yapıyor . Ancak, programınız doğru yazıldığı sürece, onun muhtemel anlambiliminde herhangi bir değişikliğe şahit olmazsınız.
Orbit'te Hafiflik Yarışları

20
Bir benzetme olarak, evinizin ön kapısının üreticisi, ortasında bir yere taktiksel olarak yerleştirilmiş bir metal parçası olsaydı daha sağlam olacağına karar vermiş olabilir. Kapınıza delikler açmadığınız ve dolayısıyla kapıları yanlış kullanmadığınız sürece asla farketmeyeceksiniz .
Orbit'te Hafiflik Yarışları

40

"Tanımlanmamış davranış tanımsızdır." (c)

Codechef üzerinde kullanılan bir derleyici aşağıdaki mantığı kullanıyor gibi görünüyor:

  1. Tanımsız davranış olamaz.
  2. i * 12345678taşar ve UB ile sonuçlanır i > 173(32 bit ints varsayılarak ).
  3. Böylece iasla geçemez 173.
  4. Bu nedenle i < 300gereksizdir ve değiştirilebilir true.

Döngünün kendisi sonsuz görünüyor. Görünüşe göre, codechef belirli bir süre sonra programı durdurur veya çıktıyı keser.


11
@ArpanMangal Çıktıdaki karakterleri saydım ve öyle görünüyor 2^16. Görünüşe göre bu, her ikisinin de çıktıyı 2^16karakterlere böldüğü bir tesadüf .
HolyBlackCat

3
@ArpanMangal: 4169 gibi nazal iblisler ve şu anda ideone ve codechef'te parti yapıyorlar. UB tanımsızdır, burun iblisleri dahil her şey olabilir. Tüm ciddiyetle, UB'yi analiz etmeye çalışmak zaman kaybıdır, bu zamanı bunun olmasını nasıl önleyeceğinizi bulmak için kullanın.
jmoreno

6
@jmoreno, derleyici tasarımıyla ilgileniyorsanız, zaman kaybı değil
user253751

2
@jmoreno Yine de bu sefer onu analiz etmek mümkündü. UB'nin işleri tam olarak nasıl bozduğunu anlamak, eğer varsa UB'nin hangi durumlarda kabul edilebilir olduğu sonucuna varmak için yararlı olabilir.
HolyBlackCat

4
@jmoreno Evrenin yaptığı her şey doğrudur (tanım gereği), peki astronomi çalışmasının amacı nedir?
user253751

11

Muhtemelen döngünüzdeki 174. yinelemede tanımsız davranışı çağırıyorsunuz forçünkü maksimum intdeğer muhtemelen 2147483647yine de 174 * 123456789ifade 2148147972, işaretli tamsayı taşması olmadığı için hangisinin tanımsız davranış olduğunu değerlendiriyor . Dolayısıyla, özellikle sizin durumunuzda optimizasyon bayrakları ayarlanmış GCC derleyicisiyle UB'nin etkilerini gözlemliyorsunuz. Derleyici muhtemelen aşağıdaki uyarıyı vererek sizi bu konuda uyarmıştır:

warning: iteration 174 invokes undefined behavior [-Waggressive-loop-optimizations]

-O2Farklı sonuçları gözlemlemek için ( ) optimizasyon bayraklarını kaldırın .


4
Tanımlanmamış davranışın geriye dönük etkileri olabileceğine dikkat etmek önemlidir - çünkü UB, 174 yinelemede gerçekleşecektir, standart döngünün ilk 173 yinelemesinin beklendiği gibi ilerlemesini bile gerektirmez!

@Hurkyl Gerçekten. UB'nin önceki ifadeler de dahil olmak üzere tüm programın UB sergilemesine neden olması biraz paradoksaldır.
Ron

12
@Ron: Paradoksal değil. Programın ya iyi tanımlanmış bir semantiği var ya da yok. Dönem. Unutmayın, C ++ kodu uygulanacak bir talimatlar dizisi değildir; öyle bir programın bir açıklama .
Orbit'te Hafiflik Yarışları

@LightnessRacesinOrbit: Bir programın kısmen belirtilmemiş ancak tamamen tanımlanmamış olmayan anlambilime sahip olması mümkündür. C Standardı bu kavramı "Sınırlı UB" olarak adlandırdığı şeye uygulamaya çalışır, ancak kullandığı dil biraz belirsizdir. Derleyicilere, tamsayı taşması gibi şeylerin ele alınmasında sınırsız olmamakla birlikte geniş bir özgürlüğe izin vermek, başka türlü yararlı pek çok optimizasyona müdahale etmez, ancak güvenilir olmayan kaynaklardan alınan verileri işlemek için dili çok daha uygun hale getirir.
süper araba

@supercat: Gerçekten de, belirtilmemiş davranış, tanımlanmamış davranışla aynı şey değildir. İkincisini tartışıyoruz
Yörüngede Hafiflik Yarışları

7

Derleyici tanımlanmamış davranış gerçekleşmez varsayabiliriz ve imzalı taşma UB, bunun nedeni asla varsayabiliriz i * 12345678 > INT_MAX, bu nedenle de i <= INT_MAX / 12345678 < 300bu nedenle işareti kaldırın i < 300.

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.