Kompilasi ini:
#include <iostream>
int main()
{
for (int i = 0; i < 4; ++i)
std::cout << i*1000000000 << std::endl;
}
dan gcc
menghasilkan peringatan berikut:
warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
std::cout << i*1000000000 << std::endl;
^
Saya mengerti ada bilangan bulat ditandatangani ditandatangani.
Yang tidak bisa saya dapatkan adalah mengapa i
nilai rusak oleh operasi luapan itu?
Saya telah membaca jawaban untuk Mengapa integer overflow pada x86 dengan GCC menyebabkan loop tak terbatas? , tapi saya masih belum jelas mengapa ini terjadi - saya mendapatkan "tidak terdefinisi" berarti "apa pun bisa terjadi", tetapi apa penyebab mendasar dari perilaku spesifik ini ?
Online: http://ideone.com/dMrRKR
Penyusun: gcc (4.8)
c++
gcc
undefined-behavior
zerkms
sumber
sumber
O2
, danO3
bendera, tetapi tidakO0
atauO1
Jawaban:
Signed integer overflow (secara tegas, tidak ada yang namanya "unsigned integer overflow") berarti perilaku tidak terdefinisi . Dan ini berarti apa pun bisa terjadi, dan mendiskusikan mengapa itu terjadi di bawah aturan C ++ tidak masuk akal.
C ++ 11 draft N3337: §5.4: 1
Kode Anda dikompilasi dengan
g++ -O3
peringatan emisi (bahkan tanpa-Wall
)Satu-satunya cara kita dapat menganalisis apa yang sedang dilakukan program, adalah dengan membaca kode assembly yang dihasilkan.
Berikut daftar perakitan lengkap:
Saya bahkan hampir tidak bisa membaca perakitan, tetapi bahkan saya bisa melihat
addl $1000000000, %edi
garis. Kode yang dihasilkan lebih miripKomentar @TC ini:
memberi saya ide untuk membandingkan kode rakitan dari kode OP ke kode rakitan dari kode berikut, tanpa perilaku yang tidak ditentukan.
Dan, pada kenyataannya, kode yang benar memiliki kondisi terminasi.
Hadapilah, Anda menulis kode buggy dan Anda akan merasa tidak enak. Tanggung konsekuensinya.
... atau, sebagai alternatif, gunakan diagnosa yang lebih baik dan alat debugging yang lebih baik - itulah gunanya:
aktifkan semua peringatan
-Wall
adalah opsi gcc yang memungkinkan semua peringatan bermanfaat tanpa positif palsu. Ini adalah jumlah minimum yang harus selalu Anda gunakan.-Wall
karena mereka dapat memperingatkan tentang kesalahan positifgunakan flag debug untuk debugging
-ftrapv
menjebak program pada overflow,-fcatch-undefined-behavior
menangkap banyak contoh perilaku tidak terdefinisi (catatan:"a lot of" != "all of them"
)Gunakan gcc
-fwrapv
1 - aturan ini tidak berlaku untuk "unsigned integer overflow", seperti §3.9.1.4 mengatakan itu
dan misalnya hasil dari
UINT_MAX + 1
didefinisikan secara matematis - dengan aturan modulithith 2 nsumber
i
sendiri terpengaruh? Secara umum perilaku tidak terdefinisi tidak memiliki efek samping yang aneh, setelah semua,i*100000000
harus menjadi nilaii
nilai lebih besar dari 2 memiliki perilaku yang tidak terdefinisi -> (2) kita dapat mengasumsikan bahwai <= 2
untuk keperluan optimasi -> (3) kondisi loop selalu benar -> (4) ) itu dioptimalkan menjadi loop tak terbatas.i
1e9 setiap iterasi (dan mengubah kondisi loop sesuai). Ini adalah optimasi yang sangat valid di bawah aturan "seolah-olah" karena program ini tidak dapat mengamati perbedaannya jika itu berperilaku baik. Sayangnya, tidak, dan optimasi "bocor".Jawaban singkat,
gcc
secara khusus telah mendokumentasikan masalah ini, kita dapat melihat bahwa dalam catatan rilis gcc 4.8 yang bertuliskan ( penekanan akan terus berlanjut ):dan memang jika kita menggunakan
-fno-aggressive-loop-optimizations
perilaku loop tak terbatas harus berhenti dan tidak dalam semua kasus yang saya uji.Jawaban panjang dimulai dengan mengetahui bahwa bilangan bulat bertanda tangan ditandatangani adalah perilaku tidak terdefinisi dengan melihat rancangan C ++ bagian standar
5
Ekspresi paragraf 4 yang mengatakan:Kita tahu bahwa standar mengatakan perilaku tidak terdefinisi tidak dapat diprediksi dari catatan yang datang dengan definisi yang mengatakan:
Tetapi apa yang bisa dilakukan oleh
gcc
pengoptimal untuk mengubah ini menjadi loop tanpa batas? Kedengarannya sangat aneh. Tapi untungnyagcc
memberi kita petunjuk untuk mencari tahu dalam peringatan:Petunjuknya adalah
Waggressive-loop-optimizations
, apa artinya itu? Untungnya bagi kami ini bukan pertama kalinya optimasi ini melanggar kode dengan cara ini dan kami beruntung karena John Regehr telah mendokumentasikan sebuah kasus dalam artikel GCC pra-4.8 Breaks Broken SPEC 2006 Benchmark yang menunjukkan kode berikut:artikel itu mengatakan:
dan kemudian mengatakan:
Jadi apa yang harus dilakukan oleh kompiler dalam beberapa kasus adalah mengasumsikan karena integer yang ditandatangani ditandatangani adalah perilaku yang tidak didefinisikan maka
i
harus selalu kurang dari4
dan dengan demikian kita memiliki loop tak terbatas.Dia menjelaskan ini sangat mirip dengan penghapusan null pointer kernel Linux terkenal di mana dalam melihat kode ini:
gcc
disimpulkan bahwa karenas
telah ditangguhkans->f;
dan karena dereferensi pointer nol adalah perilaku yang tidak terdefinisi makas
tidak boleh nol dan karena itu mengoptimalkanif (!s)
pemeriksaan di baris berikutnya.Pelajaran di sini adalah bahwa pengoptimal modern sangat agresif dalam mengeksploitasi perilaku yang tidak terdefinisi dan kemungkinan besar hanya akan menjadi lebih agresif. Jelas hanya dengan beberapa contoh kita dapat melihat pengoptimal melakukan hal-hal yang tampaknya benar-benar tidak masuk akal bagi seorang programmer tetapi dalam retrospeksi dari perspektif pengoptimal masuk akal.
sumber
tl; dr Kode menghasilkan tes integer + bilangan bulat positif == bilangan bulat negatif . Biasanya optimizer tidak mengoptimalkan ini keluar, tetapi dalam kasus spesifik
std::endl
yang digunakan berikutnya, kompilator tidak mengoptimalkan tes ini. Saya belum tahu apa yang istimewa tentangendl
belum.Dari kode rakitan di -O1 dan level yang lebih tinggi, jelas bahwa gcc mereformasi loop ke:
Nilai terbesar yang bekerja dengan benar adalah
715827882
, yaitu floor (INT_MAX/3
). Cuplikan perakitan di-O1
adalah:Perhatikan,
-1431655768
ini4 * 715827882
di komplemen 2's.Memukul
-O2
mengoptimalkan itu sebagai berikut:Jadi optimasi yang telah dibuat hanyalah bahwa
addl
itu dipindahkan lebih tinggi.Jika kita mengkompilasi ulang
715827883
sebagai gantinya maka versi -O1 identik terlepas dari angka dan nilai pengujian yang diubah. Namun, -O2 kemudian membuat perubahan:Di mana ada
cmpl $-1431655764, %esi
di-O1
, garis itu telah dihapus untuk-O2
. Optimizer harus telah memutuskan bahwa menambahkan715827883
untuk%esi
tidak pernah bisa sama-1431655764
.Ini cukup membingungkan. Menambahkan itu untuk
INT_MIN+1
memang menghasilkan hasil yang diharapkan, sehingga pengoptimal harus memutuskan bahwa%esi
tidak pernah bisaINT_MIN+1
dan saya tidak yakin mengapa itu akan memutuskan itu.Dalam contoh kerja tampaknya sama validnya untuk menyimpulkan bahwa menambahkan
715827882
ke nomor tidak bisa sama denganINT_MIN + 715827882 - 2
! (ini hanya mungkin jika sampul benar-benar terjadi), namun itu tidak mengoptimalkan garis keluar dalam contoh itu.Kode yang saya gunakan adalah:
Jika
std::endl(std::cout)
dihapus maka optimasi tidak lagi terjadi. Bahkan menggantinya denganstd::cout.put('\n'); std::flush(std::cout);
juga menyebabkan pengoptimalan tidak terjadi, meskipunstd::endl
sudah sebaris.The inlining dari
std::endl
tampaknya mempengaruhi bagian awal dari struktur loop (yang saya tidak mengerti apa yang dilakukannya tetapi saya akan mempostingnya di sini kalau-kalau ada orang lain):Dengan kode asli dan
-O2
:Dengan inlining mymanual dari
std::endl
,-O2
:Salah satu perbedaan antara keduanya adalah yang
%esi
digunakan dalam versi asli, dan%ebx
dalam versi kedua; Apakah ada perbedaan dalam semantik yang didefinisikan antara%esi
dan%ebx
secara umum? (Saya tidak tahu banyak tentang perakitan x86).sumber
Contoh lain dari kesalahan ini dilaporkan dalam gcc adalah ketika Anda memiliki loop yang mengeksekusi untuk jumlah iterasi yang konstan, tetapi Anda menggunakan variabel counter sebagai indeks ke dalam array yang memiliki kurang dari jumlah item, seperti:
Compiler dapat menentukan bahwa loop ini akan mencoba mengakses memori di luar array 'a'. Kompilator mengeluh tentang hal ini dengan pesan yang agak samar:
sumber
Tampaknya integer overflow terjadi di iterasi ke-4 (untuk
i = 3
).signed
integer overflow memanggil perilaku tidak terdefinisi . Dalam hal ini tidak ada yang bisa diprediksi. Pengulangan hanya dapat dilakukan beberapa4
kali atau mungkin tidak terhingga atau apa pun!Hasil dapat bervariasi dari kompiler ke kompiler atau bahkan untuk versi berbeda dari kompiler yang sama.
C11: 1.3.24 perilaku tidak terdefinisi:
sumber