Diperbarui, lihat di bawah!
Saya telah mendengar dan membaca bahwa C ++ 0x memungkinkan kompiler untuk mencetak "Halo" untuk cuplikan berikut
#include <iostream>
int main() {
while(1)
;
std::cout << "Hello" << std::endl;
}
Tampaknya ada hubungannya dengan utas dan kemampuan pengoptimalan. Sepertinya bagi saya ini bisa mengejutkan banyak orang.
Apakah seseorang memiliki penjelasan yang baik tentang mengapa ini perlu untuk memungkinkan? Untuk referensi, konsep C ++ 0x terbaru mengatakan di6.5/5
Sebuah loop itu, di luar pernyataan-for-init dalam kasus pernyataan for,
- tidak membuat panggilan ke fungsi I / O perpustakaan, dan
- tidak mengakses atau memodifikasi objek yang mudah menguap, dan
- tidak melakukan operasi sinkronisasi (1.10) atau operasi atom (Ayat 29)
dapat diasumsikan oleh implementasi untuk mengakhiri. [Catatan: Ini dimaksudkan untuk memungkinkan transformasi compiler, seperti penghapusan loop kosong, bahkan ketika penghentian tidak dapat dibuktikan. - catatan akhir]
Edit:
Artikel berwawasan ini mengatakan tentang teks Standar itu
Sayangnya, kata-kata "perilaku tidak terdefinisi" tidak digunakan. Namun, kapan saja standar mengatakan "kompiler dapat mengasumsikan P," tersirat bahwa sebuah program yang memiliki properti bukan-P memiliki semantik yang tidak ditentukan.
Apakah itu benar, dan apakah kompiler diperbolehkan untuk mencetak "Sampai jumpa" untuk program di atas?
Ada utas yang lebih mendalam di sini , yaitu tentang perubahan analog ke C, dimulai oleh Guy melakukan artikel terkait di atas. Di antara fakta berguna lainnya, mereka menghadirkan solusi yang tampaknya juga berlaku untuk C ++ 0x ( Pembaruan : Ini tidak akan berfungsi lagi dengan n3225 - lihat di bawah!)
endless:
goto endless;
Tampaknya kompiler tidak diizinkan untuk mengoptimalkannya, karena itu bukan loop, tetapi lompatan. Seorang pria lain merangkum perubahan yang diusulkan dalam C ++ 0x dan C201X
Dengan menulis loop, programmer menyatakan baik bahwa loop melakukan sesuatu dengan perilaku yang terlihat (Melakukan I / O, mengakses objek yang mudah menguap, atau melakukan suatu sinkronisasi atau operasi atom), atau yang akhirnya berakhir. Jika saya melanggar asumsi itu dengan menulis infinite loop tanpa efek samping, saya berbohong kepada kompiler, dan perilaku program saya tidak terdefinisi. (Jika saya beruntung, kompiler mungkin memperingatkan saya tentang hal itu.) Bahasa tidak memberikan (tidak lagi menyediakan?) Cara untuk mengekspresikan loop tanpa batas tanpa perilaku yang terlihat.
Perbarui pada 3.1.2011 dengan n3225: Komite memindahkan teks ke 1.10 / 24 dan berkata
Implementasi dapat berasumsi bahwa utas pada akhirnya akan melakukan salah satu dari yang berikut:
- mengakhiri,
- melakukan panggilan ke fungsi I / O perpustakaan,
- mengakses atau memodifikasi objek yang mudah menguap, atau
- melakukan operasi sinkronisasi atau operasi atom.
The goto
Trik akan tidak bekerja lagi!
sumber
while(1) { MyMysteriousFunction(); }
harus dapat dikompilasi secara independen tanpa mengetahui definisi fungsi misterius itu, kan? Jadi bagaimana kita bisa menentukan apakah itu membuat panggilan ke fungsi I / O perpustakaan? Dengan kata lain: pasti bahwa peluru pertama dapat diutarakan tidak membuat panggilan ke fungsi .int x = 1; for(int i = 0; i < 10; ++i) do_something(&i); x++;
menjadifor(int i = 0; i < 10; ++i) do_something(&i); int x = 2;
? Atau mungkin sebaliknya, denganx
diinisialisasi2
sebelum loop. Bisado_something
dikatakan tidak peduli tentang nilaix
, jadi itu optimasi yang sangat aman, jikado_something
tidak menyebabkan nilaii
berubah sehingga Anda berakhir di loop tak terbatas.main() { start_daemon_thread(); while(1) { sleep(1000); } }
mungkin langsung keluar daripada menjalankan daemon saya di utas latar?Jawaban:
Ya, Hans Boehm memberikan alasan untuk ini di N1528: Mengapa perilaku tidak terdefinisi untuk loop tak terbatas? , meskipun ini adalah dokumen WG14 dasar pemikirannya berlaku untuk C ++ juga dan dokumen tersebut mengacu pada WG14 dan WG21:
Satu perbedaan utama dengan C adalah bahwa C11 memberikan pengecualian untuk mengendalikan ekspresi yang merupakan ekspresi konstan yang berbeda dari C ++ dan membuat contoh spesifik Anda didefinisikan dengan baik dalam C11.
sumber
do { x = slowFunctionWithNoSideEffects(x);} while(x != 23);
kode Hoisting setelah loop yang tidak akan bergantung padax
tampaknya aman dan masuk akal, tetapi memungkinkan kompiler untuk menganggapx==23
kode tersebut tampaknya lebih berbahaya daripada berguna.Bagi saya, pembenaran yang relevan adalah:
Agaknya, ini karena membuktikan penghentian secara mekanis sulit , dan ketidakmampuan untuk membuktikan penghentian menghambat kompiler yang jika tidak dapat membuat transformasi yang bermanfaat, seperti memindahkan operasi yang tidak tergantung dari sebelum loop ke setelah atau sebaliknya, melakukan operasi pasca-loop dalam satu utas sementara loop dijalankan di yang lain, dan seterusnya. Tanpa transformasi ini, satu loop mungkin memblokir semua utas lainnya sementara mereka menunggu satu utas untuk menyelesaikan loop tersebut. (Saya menggunakan "utas" secara longgar untuk berarti segala bentuk pemrosesan paralel, termasuk aliran instruksi VLIW yang terpisah.)
EDIT: Contoh bisu:
Di sini, akan lebih cepat bagi satu utas untuk melakukan
complex_io_operation
sementara yang lain melakukan semua perhitungan rumit dalam loop. Tetapi tanpa klausa yang Anda kutip, kompiler harus membuktikan dua hal sebelum dapat melakukan optimasi: 1) itucomplex_io_operation()
tidak bergantung pada hasil dari loop, dan 2) bahwa loop akan berakhir . Membuktikan 1) cukup mudah, membuktikan 2) adalah masalah penghentian. Dengan klausa, itu mungkin mengasumsikan loop berakhir dan mendapatkan kemenangan paralelisasi.Saya juga membayangkan bahwa perancang menganggap bahwa kasus-kasus di mana loop tak terbatas terjadi dalam kode produksi sangat jarang dan biasanya hal-hal seperti loop yang digerakkan oleh peristiwa yang mengakses I / O dalam beberapa cara. Akibatnya, mereka pesimis kasus langka (loop tak terbatas) untuk mengoptimalkan kasus yang lebih umum (tidak terbatas, tetapi sulit untuk membuktikan secara mekanik tidak terbatas, loop).
Namun, itu berarti bahwa loop tak terbatas yang digunakan dalam contoh pembelajaran akan menderita sebagai hasilnya, dan akan meningkatkan gotcha dalam kode pemula. Saya tidak bisa mengatakan ini sepenuhnya hal yang baik.
EDIT: sehubungan dengan artikel berwawasan yang Anda tautkan sekarang, saya akan mengatakan bahwa "kompiler dapat menganggap X tentang program" secara logis setara dengan "jika program tidak memenuhi X, perilaku tidak ditentukan". Kita dapat menunjukkan ini sebagai berikut: misalkan ada program yang tidak memenuhi properti X. Di mana perilaku program ini didefinisikan? Standar hanya mendefinisikan perilaku dengan asumsi properti X benar. Meskipun Standar tidak secara eksplisit menyatakan perilaku yang tidak terdefinisi, itu telah menyatakan itu tidak terdefinisi oleh kelalaian.
Pertimbangkan argumen yang serupa: "kompilator dapat mengasumsikan variabel x hanya ditugaskan paling banyak sekali antara titik-titik urutan" setara dengan "menetapkan ke x lebih dari sekali antara titik-titik urutan tidak ditentukan".
sumber
complex_io_operation
.Saya pikir interpretasi yang benar adalah yang berasal dari suntingan Anda: loop tak terbatas kosong adalah perilaku yang tidak terdefinisi.
Saya tidak akan mengatakan itu perilaku yang sangat intuitif, tetapi interpretasi ini lebih masuk akal daripada yang alternatif, bahwa kompiler secara sewenang-wenang diizinkan untuk mengabaikan loop tanpa batas tanpa memanggil UB.
Jika infinite loop adalah UB, itu hanya berarti bahwa program non-terminating tidak dianggap bermakna: menurut C ++ 0x, mereka tidak memiliki semantik.
Itu memang masuk akal juga. Mereka adalah kasus khusus, di mana sejumlah efek samping tidak lagi terjadi (misalnya, tidak ada yang kembali
main
), dan sejumlah optimisasi kompiler terhambat karena harus menjaga loop tak terbatas. Sebagai contoh, memindahkan komputasi melintasi loop sangat sah jika loop tidak memiliki efek samping, karena pada akhirnya, perhitungan akan dilakukan dalam hal apa pun. Tetapi jika loop tidak pernah berakhir, kita tidak dapat dengan aman mengatur ulang kode di atasnya, karena kita mungkin hanya mengubah operasi mana yang sebenarnya dieksekusi sebelum program hang. Kecuali kita memperlakukan program gantung sebagai UB, itu.sumber
while(1){...}
. Mereka bahkan secara rutin menggunakanwhile(1);
untuk meminta reset yang dibantu oleh pengawas.Saya pikir ini sesuai dengan jenis pertanyaan ini , yang mereferensikan utas lainnya . Optimasi sesekali dapat menghapus loop kosong.
sumber
X
dan pola bit apa punx
, kompilator dapat menunjukkan bahwa loop tidak keluar tanpaX
memegang bit-polax
. Itu benar-benar hampa. JadiX
bisa dianggap memegang pola bit, dan itu seburuk UB dalam arti bahwa untuk yang salahX
danx
itu akan cepat menyebabkan beberapa. Jadi saya percaya Anda harus lebih tepat dalam standar Anda. Sulit untuk berbicara tentang apa yang terjadi "pada akhir" loop tak terbatas, dan menunjukkannya setara dengan beberapa operasi yang terbatas.Masalah yang relevan adalah bahwa kompiler diizinkan untuk menyusun ulang kode yang efek sampingnya tidak bertentangan. Urutan eksekusi yang mengejutkan dapat terjadi bahkan jika kompiler menghasilkan kode mesin non-terminating untuk infinite loop.
Saya percaya ini adalah pendekatan yang tepat. Spec bahasa mendefinisikan cara untuk menegakkan urutan eksekusi. Jika Anda ingin loop tak terbatas yang tidak dapat disusun ulang sekitar, tulis ini:
sumber
unsigned int dummy; while(1){dummy++;} fprintf(stderror,"Hey\r\n"); fprintf(stderror,"Result was %u\r\n",dummy);
, yang pertamafprintf
bisa mengeksekusi, tetapi yang kedua tidak bisa (kompiler bisa memindahkan perhitungandummy
antara keduanyafprintf
, tetapi tidak melewati yang mencetak nilainya).Saya pikir masalah ini mungkin dapat dinyatakan, sebagai "Jika sepotong kode nanti tidak tergantung pada potongan kode sebelumnya, dan potongan kode sebelumnya tidak memiliki efek samping pada bagian lain dari sistem, output kompiler dapat mengeksekusi potongan kode sebelum, setelah, atau dicampur dengan, eksekusi yang pertama, bahkan jika yang pertama berisi loop, tanpa memperhatikan kapan atau apakah kode yang lama benar-benar selesai . Sebagai contoh, kompiler dapat menulis ulang:
sebagai
Umumnya tidak masuk akal, meskipun saya mungkin khawatir bahwa:
akan menjadi
Dengan memiliki satu CPU yang menangani perhitungan dan yang lainnya menangani pembaruan progress bar, penulisan ulang akan meningkatkan efisiensi. Sayangnya, itu akan membuat pembaruan bilah kemajuan agak kurang bermanfaat daripada seharusnya.
sumber
Itu tidak dapat memutuskan untuk kompiler untuk kasus-kasus non-sepele jika itu adalah loop yang tak terbatas sama sekali.
Dalam kasus yang berbeda, dapat terjadi bahwa pengoptimal Anda akan mencapai kelas kompleksitas yang lebih baik untuk kode Anda (misalnya itu O (n ^ 2) dan Anda mendapatkan O (n) atau O (1) setelah optimasi).
Jadi, untuk memasukkan aturan seperti itu yang melarang penghapusan infinite loop ke dalam standar C ++ akan membuat banyak optimisasi menjadi tidak mungkin. Dan kebanyakan orang tidak menginginkan ini. Saya pikir ini cukup menjawab pertanyaan Anda.
Hal lain: Saya tidak pernah melihat contoh yang valid di mana Anda memerlukan loop tak terbatas yang tidak melakukan apa-apa.
Salah satu contoh yang saya dengar tentang peretasan jelek yang benar-benar harus diselesaikan sebaliknya: Ini tentang sistem tertanam di mana satu-satunya cara untuk memicu reset adalah untuk membekukan perangkat sehingga pengawas me-restart secara otomatis.
Jika Anda tahu ada contoh yang valid / bagus di mana Anda memerlukan loop tak terbatas yang tidak melakukan apa-apa, tolong beri tahu saya.
sumber
Saya pikir itu layak menunjukkan bahwa loop yang akan menjadi tak terbatas kecuali untuk kenyataan bahwa mereka berinteraksi dengan utas lainnya melalui variabel yang tidak mudah menguap, tidak tersinkronisasi sekarang dapat menghasilkan perilaku yang salah dengan kompiler baru.
Dengan kata lain, jadikan global Anda volatile - dan juga argumen yang dilewatkan ke loop seperti itu melalui pointer / referensi.
sumber
volatile
tidak perlu atau suffiicent, dan itu menyakitkan kinerja secara dramatis.