Mengapa pengoptimal GCC 6 yang ditingkatkan melanggar kode C ++ praktis?

148

GCC 6 memiliki fitur pengoptimal baru : Diasumsikan thistidak selalu nol dan dioptimalkan berdasarkan itu.

Propagasi rentang nilai sekarang mengasumsikan bahwa pointer fungsi anggota C ++ ini bukan nol. Ini menghilangkan pemeriksaan null pointer yang umum tetapi juga merusak beberapa basis kode yang tidak sesuai (seperti Qt-5, Chromium, KDevelop) . Sebagai work sementara sekitar -fno-delete-null-pointer-cek dapat digunakan. Kode yang salah dapat diidentifikasi dengan menggunakan -fsanitize = tidak terdefinisi.

Dokumen perubahan jelas menyebut ini berbahaya karena merusak sejumlah besar kode yang sering digunakan.

Mengapa asumsi baru ini mematahkan kode praktis C ++? Apakah ada pola tertentu di mana programmer yang ceroboh atau tidak informasi mengandalkan perilaku khusus yang tidak terdefinisi ini? Saya tidak dapat membayangkan siapa pun menulis if (this == NULL)karena itu sangat tidak wajar.

boot4life
sumber
21
@Ben Semoga Anda bersungguh-sungguh dengan cara yang baik. Kode dengan UB harus ditulis ulang agar tidak memanggil UB. Sesederhana itu. Heck, sering ada FAQ yang memberi tahu Anda bagaimana mencapainya. Jadi, bukan IMHO masalah nyata. Semuanya bagus.
Pasang kembali Monica
49
Saya kagum melihat orang yang membela pointer nol dereferencing dalam kode. Sangat menakjubkan.
SergeyA
19
@Ben, meledakkan perilaku tidak terdefinisi telah menjadi taktik optimasi yang sangat efektif untuk waktu yang sangat lama. Saya menyukainya, karena saya suka optimasi yang membuat kode saya berjalan lebih cepat.
SergeyA
17
Saya setuju dengan SergeyA. Seluruh brouhaha dimulai karena orang-orang tampaknya memikirkan fakta yang thisdilewatkan sebagai parameter implisit, sehingga mereka kemudian mulai menggunakannya seolah-olah itu adalah parameter eksplisit. Ini bukan. Ketika Anda melakukan dereferensi null ini, Anda memanggil UB sama seperti jika Anda meringkas pointer nol lainnya. Itu saja yang ada di sana. Jika Anda ingin meneruskan nullptrs, gunakan parameter eksplisit, DUH . Itu tidak akan lebih lambat, tidak akan ada clunkier, dan kode yang memiliki API seperti itu jauh di dalam internal, jadi memiliki ruang lingkup yang sangat terbatas. Akhir cerita saya kira.
Pasang kembali Monica
41
Kudos to GCC untuk memutus siklus kode buruk -> kompiler tidak efisien untuk mendukung kode buruk -> kode lebih buruk -> kompilasi lebih tidak efisien -> ...
MM

Jawaban:

87

Saya kira pertanyaan yang perlu dijawab mengapa orang yang bermaksud baik akan menulis cek itu.

Kasus yang paling umum adalah jika Anda memiliki kelas yang merupakan bagian dari panggilan rekursif yang terjadi secara alami.

Jika Anda memiliki:

struct Node
{
    Node* left;
    Node* right;
};

di C, Anda dapat menulis:

void traverse_in_order(Node* n) {
    if(!n) return;
    traverse_in_order(n->left);
    process(n);
    traverse_in_order(n->right);
}

Di C ++, senang membuat ini sebagai fungsi anggota:

void Node::traverse_in_order() {
    // <--- What check should be put here?
    left->traverse_in_order();
    process();
    right->traverse_in_order();
}

Pada hari-hari awal C ++ (sebelum standarisasi), ditekankan bahwa fungsi anggota adalah gula sintaksis untuk fungsi yang thisparameternya tersirat. Kode ditulis dalam C ++, dikonversi ke C yang setara dan dikompilasi. Bahkan ada contoh eksplisit yang membandingkan thisnull dengan bermakna dan kompilator Cfront asli mengambil keuntungan dari ini juga. Jadi datang dari latar belakang C, pilihan yang jelas untuk cek adalah:

if(this == nullptr) return;      

Catatan: Bjarne Stroustrup bahkan menyebutkan bahwa aturan untuk thistelah berubah selama bertahun-tahun di sini

Dan ini bekerja pada banyak kompiler selama bertahun-tahun. Ketika standardisasi terjadi, ini berubah. Dan baru-baru ini, kompiler mulai mengambil keuntungan dari memanggil fungsi anggota di mana thiskeberadaan nullptradalah perilaku yang tidak terdefinisi, yang berarti bahwa kondisi ini selalu false, dan kompiler bebas untuk menghilangkannya.

Itu berarti bahwa untuk melakukan traversal dari pohon ini, Anda perlu:

  • Lakukan semua cek sebelum menelepon traverse_in_order

    void Node::traverse_in_order() {
        if(left) left->traverse_in_order();
        process();
        if(right) right->traverse_in_order();
    }

    Ini berarti juga memeriksa di SETIAP situs panggilan jika Anda dapat memiliki root nol.

  • Jangan gunakan fungsi anggota

    Ini berarti Anda sedang menulis kode gaya C lama (mungkin sebagai metode statis), dan memanggilnya dengan objek secara eksplisit sebagai parameter. misalnya. Anda kembali menulis Node::traverse_in_order(node);daripada node->traverse_in_order();di situs panggilan.

  • Saya percaya cara termudah / paling rapi untuk memperbaiki contoh khusus ini dengan cara yang memenuhi standar adalah benar-benar menggunakan sentinel node daripada a nullptr.

    // static class, or global variable
    Node sentinel;
    
    void Node::traverse_in_order() {
        if(this == &sentinel) return;
        ...
    }

Tidak satu pun dari dua opsi pertama yang tampak menarik, dan sementara kode bisa lolos, mereka menulis kode yang buruk dengan this == nullptralih - alih menggunakan perbaikan yang tepat.

Saya menduga itulah bagaimana beberapa basis kode ini berkembang untuk this == nullptrmemeriksa di dalamnya.

jtlim
sumber
6
Bagaimana 1 == 0perilaku yang tidak terdefinisi? Sederhana saja false.
Johannes Schaub - litb
11
Pemeriksaan itu sendiri bukan perilaku yang tidak ditentukan. Itu selalu salah, dan dengan demikian dihilangkan oleh kompiler.
SergeyA
15
Hmm .. this == nullptridiom adalah perilaku tidak terdefinisi karena Anda telah memanggil fungsi anggota pada objek nullptr sebelum itu, yang tidak terdefinisi. Dan kompiler bebas untuk menghilangkan cek
jtlim
6
@ Yosua, standar pertama diterbitkan pada tahun 1998. Apa pun yang terjadi sebelumnya adalah apa pun yang diinginkan setiap implementasi. Zaman kegelapan.
SergeyA
26
Heh, wow, saya tidak percaya ada orang yang pernah menulis kode yang mengandalkan memanggil fungsi instance ... tanpa instance . Saya akan secara naluriah menggunakan kutipan yang bertanda "Lakukan semua pemeriksaan sebelum memanggil traverse_in_order", tanpa berpikir untuk thismenjadi nullable. Saya kira mungkin ini adalah manfaat dari belajar C ++ di zaman di mana SO ada untuk mengakali bahaya UB di otak saya dan menghalangi saya untuk melakukan peretasan aneh seperti ini.
underscore_d
65

Itu melakukannya karena kode "praktis" rusak dan melibatkan perilaku yang tidak terdefinisi untuk memulai. Tidak ada alasan untuk menggunakan null this, selain sebagai optimasi mikro, biasanya yang sangat prematur.

Ini adalah praktik yang berbahaya, karena penyesuaian pointer karena hierarki kelas traversal dapat mengubah nol thismenjadi bukan-nol. Jadi, paling tidak, kelas yang metodenya seharusnya bekerja dengan nol thisharuslah kelas akhir tanpa kelas dasar: ia tidak bisa berasal dari apa pun, dan tidak bisa diturunkan dari mana. Kami dengan cepat beranjak dari tempat yang praktis ke tempat yang jelek .

Secara praktis, kode tidak harus jelek:

struct Node
{
  Node* left;
  Node* right;
  void process();
  void traverse_in_order() {
    traverse_in_order_impl(this);
  }
private:
  static void traverse_in_order_impl(Node * n)
    if (!n) return;
    traverse_in_order_impl(n->left);
    n->process();
    traverse_in_order_impl(n->right);
  }
};

Jika Anda memiliki pohon kosong (mis. Root adalah nullptr), solusi ini masih mengandalkan perilaku tidak terdefinisi dengan memanggil traverse_in_order dengan nullptr.

Jika pohon itu kosong, alias nol Node* root, Anda tidak seharusnya memanggil metode non-statis apa pun. Titik. Tidak apa-apa memiliki kode pohon mirip C yang mengambil pointer contoh dengan parameter eksplisit.

Argumen di sini tampaknya bermuara pada entah bagaimana perlu menulis metode non-statis pada objek yang dapat dipanggil dari null instance pointer. Tidak perlu seperti itu. Cara C-dengan-objek menulis kode seperti itu masih jauh lebih baik di dunia C ++, karena setidaknya bisa diketik dengan aman. Pada dasarnya, nol thisadalah optimasi mikro, dengan bidang penggunaan yang sempit, yang tidak mengizinkan IMHO. Tidak ada API publik yang bergantung pada nol this.

Pasang kembali Monica
sumber
18
@ Ben, Siapa pun yang menulis kode ini salah sejak awal. Lucu bahwa Anda memberi nama proyek yang sangat rusak seperti MFC, Qt dan Chromium. Baguslah dengan mereka.
SergeyA
19
@Ben, gaya pengkodean yang mengerikan di Google sudah saya kenal. Kode Google (setidaknya tersedia untuk umum) sering ditulis dengan buruk, meskipun banyak orang percaya kode Google sebagai contoh yang cemerlang. Mungkin ini akan membuat mereka untuk meninjau kembali gaya pengkodean mereka (dan pedoman saat mereka berada di sana).
SergeyA
18
@Ben Tidak seorang pun secara retroaktif mengganti Chromium pada perangkat ini dengan Chromium yang dikompilasi menggunakan gcc 6. Sebelum Chromium akan dikompilasi menggunakan gcc 6, dan kompiler modern lainnya, itu perlu diperbaiki. Itu bukan tugas yang besar juga; yang thiscek dipetik oleh berbagai analisa kode statis, sehingga tidak seperti jika ada yang memiliki secara manual memburu mereka semua. Tambalan mungkin beberapa ratus baris perubahan sepele.
Pasang kembali Monica
8
@ Ben Secara praktis, null thisdereference adalah crash instan. Masalah-masalah ini akan ditemukan dengan sangat cepat bahkan jika tidak ada yang peduli untuk menjalankan analisa statis pada kode. C / C ++ mengikuti mantra "bayar hanya untuk fitur yang Anda gunakan". Jika Anda ingin cek, Anda harus eksplisit tentang mereka dan itu berarti tidak melakukannya this, ketika sudah terlambat, karena pengumpul menganggap thistidak nol. Kalau tidak, ia harus memeriksa this, dan untuk 99,9999% kode di luar sana, cek semacam itu adalah buang-buang waktu.
Pasang kembali Monica
10
saran saya untuk siapa pun yang berpikir standar ini rusak: gunakan bahasa yang berbeda. Tidak ada kekurangan bahasa C ++ - seperti yang tidak memiliki kemungkinan perilaku tidak terdefinisi.
MM
35

Dokumen perubahan jelas menyebut ini berbahaya karena merusak sejumlah besar kode yang sering digunakan.

Dokumen itu tidak menyebutnya berbahaya. Juga tidak mengklaim bahwa ia merusak sejumlah kode yang mengejutkan . Ini hanya menunjukkan beberapa basis kode populer yang diklaim diketahui bergantung pada perilaku tidak terdefinisi ini dan akan rusak karena perubahan kecuali jika opsi penyelesaian digunakan.

Mengapa asumsi baru ini mematahkan kode praktis C ++?

Jika kode c ++ praktis bergantung pada perilaku yang tidak terdefinisi, maka perubahan pada perilaku yang tidak terdefinisi dapat merusaknya. Inilah sebabnya mengapa UB harus dihindari, bahkan ketika sebuah program bergantung padanya tampaknya berfungsi sebagaimana dimaksud.

Apakah ada pola tertentu di mana programmer yang ceroboh atau tidak informasi mengandalkan perilaku khusus yang tidak terdefinisi ini?

Saya tidak tahu apakah itu tersebar luas anti- pola, tetapi programmer yang tidak informasi mungkin berpikir bahwa mereka dapat memperbaiki program mereka dari crash dengan melakukan:

if (this)
    member_variable = 42;

Ketika bug yang sebenarnya adalah dereferencing pointer nol di tempat lain.

Saya yakin bahwa jika programmer tidak cukup informasi, mereka akan dapat datang dengan pola (anti) yang lebih maju yang mengandalkan UB ini.

Saya tidak dapat membayangkan siapa pun menulis if (this == NULL)karena itu sangat tidak wajar.

Saya bisa.

eerorika
sumber
11
"Jika kode c ++ praktis bergantung pada perilaku yang tidak terdefinisi, maka perubahan pada perilaku yang tidak terdefinisi itu dapat merusaknya. Inilah sebabnya mengapa UB harus dihindari" * * ini
underscore_d
if(this == null) PrintSomeHelpfulDebugInformationAboutHowWeGotHere(); Seperti log yang mudah dibaca yang bagus dari urutan peristiwa yang tidak bisa dengan mudah diceritakan oleh debugger. Bersenang-senang men-debug ini sekarang tanpa menghabiskan berjam-jam menempatkan cek di mana-mana ketika ada nol acak tiba-tiba dalam kumpulan data besar, dalam kode yang belum Anda tulis ... Dan aturan UB tentang ini dibuat kemudian, setelah C ++ dibuat. Dulu valid.
Stephane Hockenhull
@StephaneHockenhull Itulah gunanya -fsanitize=null.
eerorika
@ masalah pengguna2079303: Apakah itu akan memperlambat kode produksi ke titik di mana Anda tidak dapat meninggalkan cek saat menjalankan, biaya perusahaan banyak uang? Apakah itu akan menambah ukuran dan tidak muat di flash? Apakah itu berfungsi pada semua platform target termasuk Atmel? Bisakah -fsanitize=nullmencatat kesalahan pada kartu SD / MMC pada Pin # 5,6,10,11 menggunakan SPI? Itu bukan solusi universal. Beberapa berpendapat bahwa itu bertentangan dengan prinsip-prinsip berorientasi objek untuk mengakses objek nol namun beberapa bahasa OOP memiliki objek nol yang dapat dioperasikan sehingga itu bukan aturan universal OOP. 1/2
Stephane Hockenhull
1
... ekspresi reguler yang cocok dengan file seperti itu? Mengatakan bahwa misalnya jika nilai diakses dua kali, kompiler dapat mengkonsolidasikan akses kecuali jika kode di antara mereka melakukan beberapa hal tertentu akan jauh lebih mudah daripada mencoba mendefinisikan situasi yang tepat di mana kode diizinkan untuk mengakses penyimpanan.
supercat
25

Beberapa kode "praktis" (lucu untuk mengeja "kereta") yang rusak tampak seperti ini:

void foo(X* p) {
  p->bar()->baz();
}

dan lupa untuk memperhitungkan fakta bahwa p->bar()kadang - kadang mengembalikan pointer nol, yang berarti bahwa dereferencing untuk memanggil baz()tidak ditentukan.

Tidak semua kode yang rusak berisi eksplisit if (this == nullptr)atau if (!p) return;cek. Beberapa kasus hanyalah fungsi yang tidak mengakses variabel anggota apa pun, dan tampaknya berfungsi dengan baik. Sebagai contoh:

struct DummyImpl {
  bool valid() const { return false; }
  int m_data;
};
struct RealImpl {
  bool valid() const { return m_valid; }
  bool m_valid;
  int m_data;
};

template<typename T>
void do_something_else(T* p) {
  if (p) {
    use(p->m_data);
  }
}

template<typename T>
void func(T* p) {
  if (p->valid())
    do_something(p);
  else 
    do_something_else(p);
}

Dalam kode ini ketika Anda menelepon func<DummyImpl*>(DummyImpl*)dengan pointer nol ada dereferensi "konseptual" pointer untuk memanggil p->DummyImpl::valid(), tetapi pada kenyataannya bahwa fungsi anggota hanya kembali falsetanpa mengakses *this. Itu return falsebisa diuraikan dan dalam prakteknya pointer tidak perlu diakses sama sekali. Jadi dengan beberapa kompiler tampaknya berfungsi OK: tidak ada segfault untuk dereferencing nol, p->valid()adalah salah, sehingga kode panggilan do_something_else(p), yang memeriksa pointer nol, dan tidak melakukan apa-apa. Tidak ada kerusakan atau perilaku tak terduga yang diamati.

Dengan GCC 6 Anda masih menerima panggilan p->valid(), tetapi kompiler sekarang menyimpulkan dari ekspresi yang pharus non-null (jika tidak p->valid()akan menjadi perilaku yang tidak terdefinisi) dan membuat catatan informasi tersebut. Informasi yang disimpulkan digunakan oleh pengoptimal sehingga jika panggilan untuk do_something_else(p)digarisbawahi, if (p)cek sekarang dianggap berlebihan, karena kompiler ingat bahwa itu bukan nol, dan dengan demikian menggariskan kode untuk:

template<typename T>
void func(T* p) {
  if (p->valid())
    do_something(p);
  else {
    // inlined body of do_something_else(p) with value propagation
    // optimization performed to remove null check.
    use(p->m_data);
  }
}

Ini sekarang benar-benar melakukan dereferensi pointer nol, dan kode yang sebelumnya berfungsi tampaknya berhenti bekerja.

Dalam contoh ini bug ada func, yang seharusnya memeriksa null terlebih dahulu (atau penelepon seharusnya tidak memanggilnya dengan null):

template<typename T>
void func(T* p) {
  if (p && p->valid())
    do_something(p);
  else 
    do_something_else(p);
}

Poin penting untuk diingat adalah bahwa kebanyakan optimisasi seperti ini bukan kasus dari kompiler yang mengatakan "ah, programmer menguji pointer ini terhadap null, saya akan menghapusnya hanya untuk menjengkelkan". Apa yang terjadi adalah berbagai optimisasi run-of-the-mill seperti inlining dan propagasi rentang nilai digabungkan untuk membuat cek tersebut menjadi berlebihan, karena mereka datang setelah pemeriksaan sebelumnya, atau dereferensi. Jika kompiler tahu bahwa pointer adalah bukan nol di titik A dalam suatu fungsi, dan pointer tidak berubah sebelum titik B berikutnya di fungsi yang sama, maka ia tahu itu juga bukan nol di B. Ketika inlining terjadi poin A dan B mungkin sebenarnya potongan-potongan kode yang awalnya dalam fungsi yang terpisah, tetapi sekarang digabungkan menjadi satu bagian dari kode, dan kompiler dapat menerapkan pengetahuannya bahwa pointer adalah non-null di lebih banyak tempat.

Jonathan Wakely
sumber
Apakah mungkin untuk instrumen GCC 6 untuk menghasilkan peringatan waktu kompilasi ketika bertemu dengan penggunaan seperti itu this?
jotik
3
@jotik, ^^^ apa yang dikatakan TC. Itu mungkin, tetapi Anda akan mendapatkan peringatan itu UNTUK SEMUA KODE, SELAMA WAKTU . Penyebaran rentang nilai adalah salah satu optimasi yang paling umum, yang memengaruhi hampir semua kode, di mana saja. Pengoptimal hanya melihat kode, yang dapat disederhanakan. Mereka tidak melihat "sepotong kode yang ditulis oleh orang idiot yang ingin diperingatkan jika UB bodoh mereka dioptimalkan pergi". Tidak mudah bagi kompiler untuk mengetahui perbedaan antara "redundant check yang ingin dioptimalkan programmer" dan "redundant check yang menurut programmer akan membantu, tetapi redundant".
Jonathan Wakely
1
Jika Anda ingin instrumen kode Anda untuk memberikan kesalahan runtime untuk berbagai jenis UB, termasuk penggunaan yang tidak valid this, maka gunakan saja-fsanitize=undefined
Jonathan Wakely
-25

Standar C ++ rusak dalam cara-cara penting. Sayangnya, alih-alih melindungi pengguna dari masalah ini, pengembang GCC telah memilih untuk menggunakan perilaku tidak terdefinisi sebagai alasan untuk menerapkan optimisasi marjinal, bahkan ketika telah jelas dijelaskan kepada mereka betapa berbahayanya itu.

Di sini orang yang jauh lebih pintar daripada yang saya jelaskan dengan sangat terperinci. (Dia berbicara tentang C tetapi situasinya sama di sana).

Mengapa itu berbahaya?

Cukup mengkompilasi ulang kode yang sebelumnya berfungsi, kode aman dengan versi yang lebih baru dari kompiler dapat memperkenalkan kerentanan keamanan . Sementara perilaku baru dapat dinonaktifkan dengan flag, makefile yang ada tidak memiliki set flag itu, jelas. Dan karena tidak ada peringatan yang dihasilkan, tidak jelas bagi pengembang bahwa perilaku yang sebelumnya masuk akal telah berubah.

Dalam contoh ini, pengembang telah menyertakan pemeriksaan untuk integer overflow, menggunakan assert, yang akan menghentikan program jika disediakan panjang yang tidak valid. Tim GCC menghapus cek dengan dasar bahwa integer overflow tidak terdefinisi, oleh karena itu cek dapat dihapus. Ini menghasilkan contoh nyata nyata basis kode ini yang dibuat kembali menjadi rentan setelah masalah diperbaiki.

Baca semuanya. Cukup membuat Anda menangis.

OK, tapi bagaimana dengan yang ini?

Jauh ketika, ada idiom yang cukup umum yang berbunyi seperti ini:

 OPAQUEHANDLE ObjectType::GetHandle(){
    if(this==NULL)return DEFAULTHANDLE;
    return mHandle;

 }

 void DoThing(ObjectType* pObj){
     osfunction(pObj->GetHandle(), "BLAH");
 }

Jadi idiomanya adalah: Jika pObjbukan nol, Anda menggunakan pegangan yang dikandungnya, jika tidak, Anda menggunakan pegangan bawaan. Ini diringkas dalam GetHandlefungsi.

Kuncinya adalah bahwa memanggil fungsi non-virtual sebenarnya tidak menggunakan thispointer, jadi tidak ada pelanggaran akses.

Saya masih belum mengerti

Ada banyak kode yang ditulis seperti itu. Jika seseorang hanya mengkompilasi ulang, tanpa mengubah saluran, setiap panggilan DoThing(NULL)adalah bug yang menabrak - jika Anda beruntung.

Jika Anda tidak beruntung, panggilan ke bug yang menabrak menjadi kerentanan eksekusi jarak jauh.

Ini dapat terjadi bahkan secara otomatis. Anda punya sistem build otomatis, kan? Meng-upgrade ke kompiler terbaru tidak berbahaya, bukan? Tapi sekarang tidak - tidak jika kompiler Anda adalah GCC.

Baiklah, beri tahu mereka!

Mereka sudah diberitahu. Mereka melakukan ini dengan pengetahuan penuh tentang konsekuensinya.

tapi kenapa?

Siapa yang bisa bilang? Mungkin:

  • Mereka menghargai kemurnian ideal bahasa C ++ di atas kode aktual
  • Mereka percaya orang harus dihukum karena tidak mengikuti standar
  • Mereka tidak memiliki pemahaman tentang realitas dunia
  • Mereka ... memperkenalkan bug dengan sengaja. Mungkin untuk pemerintah asing. Dimana kamu tinggal? Semua pemerintah asing bagi sebagian besar dunia, dan sebagian besar bermusuhan dengan sebagian dunia.

Atau mungkin sesuatu yang lain. Siapa yang bisa bilang?

Ben
sumber
32
Tidak setuju dengan setiap baris jawaban. Komentar yang sama dibuat untuk pengoptimalan aliasing yang ketat, dan semoga saja ditolak sekarang. Solusinya adalah mengedukasi pengembang, bukan mencegah optimasi berdasarkan kebiasaan pengembangan yang buruk.
SergeyA
30
Saya memang pergi dan membaca semuanya seperti yang Anda katakan, dan memang saya menangis, tetapi terutama pada kebodohan Felix yang saya pikir bukan yang ingin Anda sampaikan ...
Mike Vine
33
Diturunkan karena kata-kata kasar yang tidak berguna. "Mereka ... memperkenalkan serangga dengan sengaja. Mungkin untuk pemerintah asing." Betulkah? Ini bukan / r / konspirasi.
isanae
31
Pemrogram yang layak berulang-ulang mengulangi mantra tidak memohon perilaku yang tidak terdefinisi , namun kebiasaan ini terus berjalan dan tetap melakukannya. Dan lihat apa yang terjadi. Saya tidak punya simpati sama sekali. Ini adalah kesalahan pengembang, sesederhana itu. Mereka harus bertanggung jawab. Ingat bahwa? Tanggung jawab pribadi? Orang-orang mengandalkan mantra Anda "tetapi bagaimana dalam praktiknya !" Persis bagaimana situasi ini muncul di tempat pertama. Menghindari omong kosong seperti ini justru mengapa standar ada di tempat pertama. Kode untuk standar, dan Anda tidak akan punya masalah. Titik.
Lightness Races dalam Orbit
18
"Cukup mengkompilasi ulang kode yang sebelumnya berfungsi dan aman dengan versi yang lebih baru dari kompiler dapat memperkenalkan kerentanan keamanan" - itu selalu terjadi . Kecuali Anda ingin mengamanatkan bahwa satu versi dari satu kompiler adalah satu-satunya kompiler yang akan diizinkan untuk selamanya. Apakah Anda ingat kembali ketika kernel linux hanya bisa dikompilasi dengan tepatnya gcc 2.7.2.1? Proyek gcc bahkan mendapatkan percabangan karena orang-orang muak dengan bullcrap. Butuh waktu lama untuk melewati itu.
MM