Kode berikut masuk ke loop tak terbatas pada GCC:
#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;
}
Jadi, inilah masalahnya: Signed integer overflow adalah perilaku yang secara teknis tidak terdefinisi. Tetapi GCC pada x86 mengimplementasikan bilangan bulat aritmatika menggunakan instruksi integer x86 - yang membungkus overflow.
Oleh karena itu, saya akan berharap untuk membungkus overflow - meskipun fakta bahwa itu adalah perilaku yang tidak terdefinisi. Tapi jelas bukan itu masalahnya. Jadi apa yang saya lewatkan?
Saya menyusun ini menggunakan:
~/Desktop$ g++ main.cpp -O2
Output GCC:
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
0
0
0
... (infinite loop)
Dengan optimisasi dinonaktifkan, tidak ada loop tak terbatas dan output benar. Visual Studio juga mengkompilasi dengan benar dan memberikan hasil sebagai berikut:
Output yang Benar:
~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3
Berikut beberapa variasi lain:
i *= 2; // Also fails and goes into infinite loop.
i <<= 1; // This seems okay. It does not enter infinite loop.
Inilah semua informasi versi yang relevan:
~/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$
Jadi pertanyaannya adalah: Apakah ini bug di GCC? Atau apakah saya salah paham tentang bagaimana GCC menangani bilangan bulat aritmatika?
* Saya juga menandai C ini, karena saya menganggap bug ini akan mereproduksi dalam C. (Saya belum memverifikasi itu.)
EDIT:
Inilah perakitan loop: (jika saya mengenalinya dengan benar)
.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
gcc -S
.Jawaban:
Ketika standar mengatakan itu perilaku yang tidak terdefinisi, itu artinya . Segalanya bisa terjadi. "Apa pun" termasuk "biasanya bilangan bulat membungkus, tetapi terkadang hal aneh terjadi".
Ya, pada CPU x86, integer biasanya membungkus seperti yang Anda harapkan. Ini adalah salah satu pengecualian itu. Compiler menganggap Anda tidak akan menyebabkan perilaku tidak terdefinisi, dan mengoptimalkan tes loop. Jika Anda benar-benar ingin sampul, sampaikan
-fwrapv
keg++
ataugcc
saat kompilasi; ini memberi Anda semantik overflow yang didefinisikan dengan baik (dua-komplemen), tetapi dapat merusak kinerja.sumber
-fwrapv
. Terima kasih telah menunjukkan ini.Sederhana: Perilaku tidak terdefinisi - terutama dengan pengoptimalan (
-O2
) dihidupkan - artinya segala sesuatu dapat terjadi.Kode Anda berperilaku seperti yang Anda harapkan tanpa
-O2
saklar.Ini berfungsi cukup baik dengan icl dan tcc, tetapi Anda tidak dapat mengandalkan hal-hal seperti itu ...
Menurut ini , optimasi gcc sebenarnya mengeksploitasi integer overflow yang ditandatangani. Ini berarti bahwa "bug" adalah dengan desain.
sumber
for (j = i; j < i + 10; ++j) ++k;
, itu hanya akan ditetapkank = 10
, karena ini akan selalu benar jika tidak ada luapan yang masuk.Yang penting untuk dicatat di sini adalah bahwa program C ++ ditulis untuk mesin abstrak C ++ (yang biasanya ditiru melalui instruksi perangkat keras). Fakta bahwa Anda mengkompilasi untuk x86 sama sekali tidak relevan dengan fakta bahwa ini memiliki perilaku yang tidak terdefinisi.
Kompiler bebas menggunakan keberadaan perilaku tidak terdefinisi untuk meningkatkan optimisasinya, (dengan menghapus conditional dari loop, seperti dalam contoh ini). Tidak ada pemetaan yang dijamin, atau bahkan bermanfaat, antara konstruksi level C ++ dan konstruksi kode level x86 selain dari persyaratan bahwa kode mesin akan, ketika dijalankan, menghasilkan hasil yang diminta oleh mesin abstrak C ++.
sumber
// overflow tidak ditentukan.
Dengan -fwrapv sudah benar. -fwrapv
sumber
Tolong orang, perilaku tidak terdefinisi persis seperti itu, tidak terdefinisi . Itu berarti apa pun bisa terjadi. Dalam prakteknya (seperti dalam kasus ini), kompiler bebas untuk menganggapnya tidakdipanggil, dan lakukan apa pun yang diinginkan jika itu bisa membuat kode lebih cepat / lebih kecil. Apa yang terjadi dengan kode yang tidak boleh dijalankan adalah dugaan siapa pun. Itu akan tergantung pada kode di sekitarnya (tergantung pada itu, kompiler juga bisa menghasilkan kode yang berbeda), variabel / konstanta yang digunakan, bendera kompiler, ... Oh, dan kompiler dapat diperbarui dan menulis kode yang sama secara berbeda, atau Anda bisa dapatkan kompiler lain dengan tampilan berbeda pada pembuatan kode. Atau hanya mendapatkan mesin yang berbeda, bahkan model lain dalam garis arsitektur yang sama bisa sangat baik memiliki perilaku yang tidak terdefinisi itu sendiri (mencari opcode yang tidak terdefinisi, beberapa programmer yang giat menemukan bahwa pada beberapa mesin awal itu kadang-kadang melakukan hal-hal yang bermanfaat ...) . Tidak ada+ msgstr "kompiler memberikan perilaku pasti pada perilaku tidak terdefinisi". Ada area yang didefinisikan implementasi, dan di sana Anda harus dapat mengandalkan kompilator yang berperilaku konsisten.
sumber
Sekalipun kompiler harus menetapkan bahwa bilangan bulat bilangan bulat harus dianggap sebagai bentuk "tidak kritis" dari Perilaku Tidak Terdefinisi (sebagaimana didefinisikan dalam Lampiran L), hasil dari bilangan bulat bilangan bulat harus, tidak ada janji platform spesifik dari perilaku yang lebih spesifik, menjadi setidaknya dianggap sebagai "nilai sebagian tidak tentu". Berdasarkan aturan tersebut, menambahkan 1073741824 + 1073741824 dapat secara sewenang-wenang dianggap menghasilkan 2147483648 atau -2147483648 atau nilai lain yang kongruen dengan 2147483648 mod 4294967296, dan nilai yang diperoleh dengan penambahan dapat secara sewenang-wenang dianggap sebagai nilai apa pun yang sesuai dengan nilai 0 mod 42949676.
Aturan yang memungkinkan overflow untuk menghasilkan "nilai-nilai yang sebagian tak tentu" akan didefinisikan dengan cukup baik untuk mematuhi surat dan semangat Lampiran L, tetapi tidak akan mencegah kompiler membuat kesimpulan umum yang sama-sama berguna seperti yang akan dibenarkan jika luapan tidak dibatasi Perilaku tidak terdefinisi. Ini akan mencegah kompiler membuat beberapa "optimisasi" palsu yang efek utamanya dalam banyak kasus adalah mengharuskan pemrogram menambahkan kekacauan tambahan pada kode yang tujuan utamanya adalah untuk mencegah "optimasi" tersebut; apakah itu akan menjadi hal yang baik atau tidak tergantung pada sudut pandang seseorang.
sumber