Bagaimana saya tahu jika kompiler melanggar kode saya dan apa yang harus saya lakukan jika itu adalah kompiler?

14

Kadang-kadang kode C ++ tidak akan berfungsi ketika dikompilasi dengan beberapa tingkat optimasi. Mungkin kompiler melakukan optimasi yang memecahkan kode atau mungkin kode yang berisi perilaku tidak terdefinisi yang memungkinkan kompiler melakukan apa pun yang dirasakannya.

Misalkan saya memiliki beberapa kode yang rusak ketika dikompilasi dengan level optimisasi yang lebih tinggi saja. Bagaimana saya tahu apakah itu kode atau kompiler dan apa yang harus saya lakukan jika kompiler?

sharptooth
sumber
43
Kemungkinan besar itu Anda.
littleadv
9
@littleadv, bahkan versi terbaru gcc dan msvc penuh dengan bug, jadi saya tidak akan begitu yakin.
SK-logic
3
Anda memiliki semua peringatan diaktifkan?
@ Thorbjørn Ravn Andersen: Ya, saya sudah mengaktifkannya.
sharptooth
3
FWIW: 1) Saya mencoba untuk tidak melakukan sesuatu yang rumit yang dapat menggoda kompiler untuk mengacaukan, 2) satu-satunya tempat masalah flag optimasi (untuk kecepatan) adalah dalam kode di mana penghitung program menghabiskan sebagian besar waktunya. Kecuali jika Anda menulis loop cpu ketat, pada banyak aplikasi PC menghabiskan seluruh waktunya jauh di perpustakaan, atau di I / O. Dalam aplikasi semacam itu, tombol / O tidak membantu sama sekali.
Mike Dunlavey

Jawaban:

19

Saya akan mengatakan itu adalah taruhan yang aman bahwa, dalam sebagian besar kasus, itu adalah kode Anda, bukan kompiler, yang rusak. Dan bahkan dalam kasus luar biasa ketika itu adalah kompiler, Anda mungkin menggunakan beberapa fitur bahasa yang tidak jelas dengan cara yang tidak biasa, di mana kompiler spesifik tidak disiapkan; dengan kata lain, Anda kemungkinan besar dapat mengubah kode Anda menjadi lebih idiomatis dan menghindari titik lemah dari kompiler.

Bagaimanapun, jika Anda dapat membuktikan bahwa Anda menemukan bug kompiler (berdasarkan spesifikasi bahasa), laporkan itu kepada pengembang kompiler, sehingga mereka dapat memperbaikinya beberapa saat.

Péter Török
sumber
@ SK-logika, cukup adil, saya tidak punya statistik untuk mendukungnya. Ini didasarkan pada pengalaman saya sendiri, dan saya akui saya jarang memperluas batasan bahasa dan / atau kompiler - orang lain mungkin lebih sering melakukannya.
Péter Török
(1) @ SK-Logic: Baru saja menemukan bug penyusun C ++, kode yang sama, dicoba pada satu kompiler dan berhasil, dicoba di yang lain lagi.
umlcat
8
@umlcat: kemungkinan besar itu kode Anda tergantung pada perilaku yang tidak ditentukan; pada satu kompiler itu sesuai dengan harapan Anda, yang lain tidak. itu tidak berarti itu rusak.
Javier
@Ritch Melton, apakah Anda pernah menggunakan KPP?
SK-logic
1
Saya setuju dengan Crashworks, ketika berbicara tentang konsol game. Sama sekali tidak aneh menemukan bug kompiler esoterik dalam situasi khusus itu. Jika Anda menargetkan PC biasa, menggunakan kompiler yang banyak digunakan, maka sangat tidak mungkin Anda akan menemukan bug kompiler yang belum pernah dilihat siapa pun sebelumnya.
Trevor Powell
14

Seperti biasa, seperti bug lainnya: lakukan percobaan terkontrol. Persempit area yang mencurigakan, matikan optimisasi untuk yang lain dan mulailah memvariasikan optimasi yang diterapkan pada potongan kode itu. Setelah Anda mendapatkan reproduksibilitas 100%, mulailah memvariasikan kode Anda, perkenalkan hal-hal yang dapat mematahkan optimisasi tertentu (misalnya, perkenalkan kemungkinan penunjuk aliasing, masukkan panggilan eksternal dengan efek samping potensial, dll.). Melihat kode rakitan dalam debugger mungkin membantu juga.

Logika SK
sumber
mungkin membantu dengan apa? Jika itu adalah bug penyusun - jadi apa?
littleadv
2
@littleadv, jika ini adalah bug penyusun, Anda dapat mencoba memperbaikinya (atau melaporkannya dengan benar, dengan detail lengkap), atau Anda dapat mengetahui cara menghindarinya di masa mendatang, jika Anda ditakdirkan untuk terus menggunakan ini versi kompiler Anda untuk sementara waktu. Jika ada sesuatu dengan kode Anda sendiri, salah satu dari banyak isu C + + - ish borderline, pengawasan semacam ini juga membantu dalam memperbaiki bug dan menghindari jenisnya di masa depan.
SK-logic
Jadi seperti yang saya katakan dalam jawaban saya - selain melaporkan, tidak ada banyak perbedaan dalam perawatan, terlepas dari kesalahan siapa itu.
littleadv
3
@littleadv, tanpa memahami sifat masalah Anda kemungkinan akan menghadapinya lagi dan lagi. Dan seringkali ada kemungkinan untuk memperbaiki kompiler sendiri. Dan, ya, itu bukan "tidak mungkin" sama sekali untuk menemukan bug di kompiler C ++, sayangnya.
SK-logic
10

Periksa kode rakitan yang dihasilkan dan lihat apakah itu sesuai dengan yang diminta sumber Anda. Ingatlah bahwa kemungkinannya sangat tinggi sehingga kode Anda salah dengan cara yang tidak jelas.

Loren Pechtel
sumber
1
Ini adalah satu-satunya jawaban untuk pertanyaan ini. Pekerjaan kompiler, dalam hal ini, adalah untuk membawa Anda dari C ++ ke bahasa assembly. Anda pikir itu adalah kompiler ... periksa kerja kompiler. Sesederhana itu.
old_timer
7

Dalam lebih dari 30 tahun pemrograman, jumlah bug kompiler (pembuatan kode) asli yang saya temukan masih hanya ~ 10. Jumlah bug saya sendiri (dan orang lain) yang saya temukan dan perbaiki pada periode yang sama mungkin > 10.000. "Aturan praktis" saya kemudian adalah bahwa probabilitas setiap bug yang diberikan karena kompiler adalah <0,001.

Paul R
sumber
1
Anda beruntung. Rata-rata saya adalah sekitar 1 bug yang sangat buruk dalam sebulan, dan masalah perbatasan kecil jauh lebih sering terjadi. Dan semakin tinggi level optimisasi yang Anda gunakan, semakin tinggi pula peluang kesalahan penyusun. Jika Anda mencoba menggunakan -O3 dan KPP, Anda akan sangat beruntung tidak menemukan beberapa dari mereka dalam waktu singkat. Dan saya hanya menghitung di sini bug dalam versi rilis - sebagai pengembang kompiler, saya menghadapi lebih banyak masalah seperti itu dalam pekerjaan saya, tetapi itu tidak masuk hitungan. Saya hanya tahu betapa mudahnya mengacaukan kompiler.
SK-logic
2
25 tahun dan saya juga telah melihat sangat banyak. Kompiler semakin buruk setiap tahun.
old_timer
5

Saya mulai menulis komentar dan kemudian memutuskan terlalu lama dan terlalu banyak intinya.

Saya berpendapat bahwa itu adalah kode Anda yang rusak. Jika Anda menemukan bug di kompiler - Anda harus melaporkannya ke pengembang kompiler, tetapi di situlah perbedaannya berakhir.

Solusinya adalah mengidentifikasi konstruk yang menyinggung, dan refactor sehingga akan melakukan logika yang sama secara berbeda. Itu kemungkinan besar akan memecahkan masalah, apakah bug ada di pihak Anda atau di kompiler.

littleadv
sumber
5
  1. Baca kembali kode Anda secara keseluruhan. Pastikan Anda tidak melakukan hal-hal dengan efek samping dalam ASSERT, atau pernyataan khusus debug (atau lebih umum, konfigurasi) lainnya. Juga ingat bahwa dalam membangun memori debug diinisialisasi secara berbeda - nilai-nilai penunjuk yang dapat Anda periksa di sini: Debugging - Representasi Alokasi Memori . Saat menjalankan dari dalam Visual Studio Anda hampir selalu menggunakan Debug Heap (bahkan dalam mode rilis) kecuali jika Anda secara eksplisit menentukan dengan variabel lingkungan bahwa ini bukan yang Anda inginkan.
  2. Periksa bangunan Anda. Adalah umum untuk mendapatkan masalah dengan build kompleks di tempat lain daripada kompiler yang sebenarnya - dependensi sering menjadi biang keladinya. Saya tahu bahwa "sudahkah Anda mencoba membangun kembali sepenuhnya" hampir sama menyebalkannya dengan jawaban "apakah Anda sudah mencoba menginstal ulang windows", tetapi itu sering membantu. Coba: a) Mulai ulang. b) Menghapus semua file antara dan output MANUAL dan membangun kembali.
  3. Lihatlah kode Anda untuk memeriksa lokasi potensial di mana Anda mungkin memohon perilaku tidak terdefinisi. Jika Anda telah bekerja di C ++ untuk sementara waktu, Anda akan tahu ada beberapa tempat di mana Anda berpikir "Saya TIDAK BENAR-BENAR yakin saya boleh berasumsi bahwa ..." - cari di google atau tanyakan di sini tentang hal itu jenis kode untuk melihat apakah itu perilaku yang tidak terdefinisi atau tidak.
  4. Jika itu masih tidak menjadi masalah, hasilkan output yang sudah diproses untuk file yang menyebabkan masalah. Perluasan makro yang tidak terduga dapat menyebabkan semua jenis kesenangan (saya diingatkan saat seorang kolega memutuskan makro dengan nama H akan menjadi ide yang bagus ...). Periksa output yang telah diproses untuk perubahan tak terduga antara konfigurasi proyek Anda.
  5. Pilihan terakhir - sekarang Anda benar-benar berada di tanah penyusun bug - lihat output perakitan. Ini mungkin membutuhkan beberapa penggalian dan pertempuran hanya untuk mengetahui apa yang sebenarnya dilakukan majelis, tetapi sebenarnya cukup informatif. Anda dapat menggunakan keterampilan yang Anda ambil di sini untuk mengevaluasi optimasi mikro juga, jadi semuanya tidak hilang.
Joris Timmermans
sumber
+1 untuk "perilaku tidak terdefinisi." Saya digigit oleh yang itu. Menulis beberapa kode yang bergantung pada int + intoverflow persis seperti jika dikompilasi ke instruksi ADD perangkat keras. Ini berfungsi dengan baik ketika dikompilasi dengan versi GCC yang lebih lama, tetapi tidak ketika dikompilasi dengan kompiler yang lebih baru. Tampaknya orang-orang baik di GCC memutuskan bahwa karena hasil bilangan bulat bilangan bulat tidak ditentukan, pengoptimal mereka dapat beroperasi dengan asumsi bahwa hal itu tidak pernah terjadi. Itu dioptimalkan cabang penting keluar dari kode.
Solomon Slow
2

Jika Anda ingin tahu apakah itu kode atau kompiler, Anda harus mengetahui spesifikasi C ++ dengan sempurna.

Jika keraguan tetap ada, Anda harus benar-benar mengetahui perakitan x86.

Jika Anda tidak ingin mempelajari keduanya dengan sempurna, maka hampir pasti merupakan perilaku yang tidak ditentukan yang diselesaikan oleh kompiler Anda berbeda-beda tergantung pada level optimisasi.

mouviciel
sumber
(+1) @mouviciel: Ini juga tergantung apakah fitur yang didukung oleh kompiler, bahkan, jika dalam spesifikasi. Saya memiliki bug aneh dengan gcc. Saya mendeklarasikan "struktur c polos" dengan "penunjuk fungsi", yang diizinkan dalam spesifikasi, tetapi berfungsi dalam beberapa situasi, dan tidak di yang lain.
umlcat
1

Mendapatkan kesalahan kompilasi pada kode standar atau kesalahan kompilasi internal lebih mungkin terjadi daripada pengoptimal yang salah. Tetapi saya telah mendengar tentang kompiler mengoptimalkan loop salah melupakan beberapa efek samping penyebab metode.

Saya tidak punya saran tentang cara mengetahui apakah itu Anda atau kompiler. Anda dapat mencoba kompiler lain.

Suatu hari saya bertanya-tanya apakah itu kode saya atau tidak dan seseorang menyarankan valgrind kepada saya. Saya menghabiskan 5 atau 10 menit untuk menjalankan program saya dengan itu (saya pikir valgrind --leak-check=yes myprog arg1 arg2melakukannya tetapi saya bermain dengan pilihan lain) dan segera menunjukkan kepada saya SATU baris yang dijalankan di bawah satu kasus tertentu yang merupakan masalah. Kemudian aplikasi saya berjalan dengan lancar sejak itu tanpa crash aneh, kesalahan atau perilaku aneh. valgrind atau alat lain seperti itu adalah cara yang baik untuk mengetahui apakah itu kode Anda.

Catatan: Saya pernah bertanya-tanya mengapa kinerja aplikasi saya payah. Ternyata semua masalah kinerja saya dalam satu baris juga. Saya menulis for(int i=0; i<strlen(sz); ++i) {. Sz itu beberapa mb. Untuk beberapa alasan, kompiler menjalankan strlen setiap kali bahkan setelah optimasi. Satu baris bisa menjadi masalah besar. Dari pertunjukan hingga tabrakan


sumber
1

Situasi yang semakin umum adalah bahwa kompiler memecah kode yang ditulis untuk dialek C yang mendukung perilaku yang tidak diamanatkan oleh Standar, dan memungkinkan kode penargetan dialek itu menjadi lebih efisien daripada kode yang benar-benar sesuai. Dalam kasus seperti itu, akan tidak adil untuk menggambarkan kode "rusak" yang 100% dapat diandalkan pada kompiler yang mengimplementasikan dialek target, atau menggambarkan sebagai "rusak" kompiler yang memproses dialek yang tidak mendukung semantik yang diperlukan . Alih-alih, masalahnya hanya berasal dari fakta bahwa bahasa yang diproses oleh kompiler modern dengan optimisasi diaktifkan berbeda dari dialek yang dulu populer (dan masih diproses oleh banyak kompiler dengan optimisasi dinonaktifkan, atau oleh beberapa bahkan dengan optimasi diaktifkan).

Sebagai contoh, banyak kode ditulis untuk dialek yang mengakui sejumlah pola penunjuk yang sah yang tidak diamanatkan oleh interpretasi gcc tentang Standar, dan memanfaatkan pola tersebut untuk memungkinkan terjemahan langsung kode menjadi lebih mudah dibaca dan efisien daripada yang mungkin di bawah interpretasi gcc tentang Standar C. Kode seperti itu mungkin tidak kompatibel dengan gcc, tetapi itu tidak berarti bahwa itu rusak. Itu hanya bergantung pada ekstensi yang hanya didukung gcc dengan optimisasi dinonaktifkan.

supercat
sumber
Ya, tentu saja tidak ada yang salah dengan pengkodean C standar X + ekstensi Y dan Z, selama itu memberi Anda keuntungan signifikan, Anda tahu Anda melakukan itu, dan Anda mendokumentasikannya dengan seksama. Sayangnya, ketiga kondisi tersebut biasanya tidak terpenuhi , dan dengan demikian wajar untuk mengatakan bahwa kode tersebut rusak.
Deduplicator
@Dupuplikator: kompiler C89 dipromosikan sebagai kompatibel ke atas dengan pendahulunya, dan juga untuk C99, dll. Sementara C89 tidak menetapkan persyaratan pada perilaku yang sebelumnya telah ditentukan pada beberapa platform tetapi tidak pada yang lain, kompatibilitas ke atas akan menyarankan bahwa kompiler C89 untuk platform yang telah menganggap perilaku sebagai didefinisikan harus terus melakukannya; alasan untuk mempromosikan tipe-tipe pendek yang tidak ditandatangani menjadi tanda-tanda akan menyarankan bahwa para penulis Standar mengharapkan penyusun untuk berperilaku seperti itu apakah Standar mengamanatkannya atau tidak. Selanjutnya ...
supercat
... interpretasi ketat dari aturan aliasing akan membuang kompatibilitas ke atas jendela dan membuat banyak jenis kode tidak bisa dijalankan, tetapi beberapa penyesuaian kecil (misalnya mengidentifikasi beberapa pola di mana aliasing lintas-tipe harus diharapkan dan karenanya diizinkan) akan menyelesaikan kedua masalah . Seluruh tujuan aturan yang dinyatakan adalah untuk menghindari keharusan penyusun membuat asumsi aliasing "pesimistis", tetapi diberi "float x", seandainya anggapan bahwa "foo ((int *) & x)" dapat memodifikasi x bahkan jika "foo" tidak bisa menulis ke petunjuk apa pun dari tipe 'float * "atau" char * "dianggap" pesimistis "atau" jelas "?
supercat
0

Mengisolasi titik bermasalah dan membandingkan perilaku yang diamati dengan apa yang harus terjadi sesuai dengan spesifikasi bahasa. Jelas tidak mudah, tetapi itulah yang harus Anda lakukan untuk mengetahui (dan tidak hanya berasumsi ).

Saya mungkin tidak akan selincis itu. Sebaliknya, saya akan meminta forum / milis dukungan pembuat kompiler. Jika itu benar-benar bug di kompiler, maka mereka mungkin memperbaikinya. Mungkin itu akan menjadi kode saya. Sebagai contoh, spesifikasi bahasa mengenai visibilitas memori dalam threading bisa sangat berlawanan dengan intuisi, dan mereka bisa menjadi jelas hanya ketika menggunakan beberapa flag optimasi tertentu, pada beberapa perangkat keras tertentu (!). Beberapa perilaku dapat tidak ditentukan oleh spec, sehingga dapat bekerja dengan beberapa kompiler / beberapa flag, dan tidak bekerja dengan yang lain, dll.

Joonas Pulakka
sumber
0

Kemungkinan besar kode Anda memiliki beberapa perilaku yang tidak terdefinisi (seperti yang dijelaskan orang lain, Anda lebih cenderung memiliki bug dalam kode Anda daripada di kompiler, bahkan jika kompiler C ++ sangat kompleks sehingga mereka memiliki bug; bahkan spesifikasi C ++ memiliki bug desain) . Dan UB dapat berada di sini bahkan jika executable yang dikompilasi kebetulan bekerja (dengan nasib buruk).

Jadi, Anda harus membaca blog Lattner ' What Every Programer C harus tahu tentang perilaku yang tidak terdefinisi (sebagian besar berlaku untuk C ++ 11 juga).

Alat valgrind , dan -fsanitize= opsi instrumentasi terbaru untuk GCC (atau Dentang / LLVM ), juga harus membantu. Dan tentu saja, aktifkan semua peringatan:g++ -Wall -Wextra

Basile Starynkevitch
sumber