Setelah melihat sekelompok dari lainnya pertanyaan dan mereka jawaban , saya mendapatkan kesan bahwa tidak ada kesepakatan luas tentang apa yang "volatile" kata kunci dalam C berarti persis.
Bahkan standar itu sendiri tampaknya tidak cukup jelas bagi semua orang untuk menyetujui apa artinya .
Di antara masalah lain:
- Tampaknya memberikan jaminan yang berbeda tergantung pada perangkat keras Anda dan tergantung pada kompiler Anda.
- Ini mempengaruhi pengoptimalan kompiler tetapi bukan pengoptimalan perangkat keras, jadi pada prosesor tingkat lanjut yang melakukan pengoptimalan run-time sendiri, bahkan tidak jelas apakah kompiler dapat mencegah pengoptimalan apa pun yang ingin Anda cegah. (Beberapa kompiler memang menghasilkan instruksi untuk mencegah beberapa optimasi perangkat keras pada beberapa sistem, tetapi ini tampaknya tidak distandarisasi dengan cara apa pun.)
Untuk meringkas masalah, tampak (setelah membaca banyak) bahwa "volatile" menjamin sesuatu seperti: Nilai akan dibaca / ditulis tidak hanya dari / ke register, tetapi setidaknya ke cache L1 inti, dalam urutan yang sama yang baca / tulis muncul dalam kode. Tapi ini tampaknya tidak berguna, karena membaca / menulis dari / ke register sudah cukup dalam utas yang sama, sementara berkoordinasi dengan L1 cache tidak menjamin apa pun lebih lanjut mengenai koordinasi dengan utas lainnya. Saya tidak bisa membayangkan kapan bisa penting untuk melakukan sinkronisasi hanya dengan cache L1.
PENGGUNAAN 1.
Satu-satunya penggunaan volatile yang disepakati secara luas tampaknya untuk sistem lama atau tertanam di mana lokasi memori tertentu dipetakan perangkat keras ke fungsi I / O, seperti sedikit dalam memori yang mengontrol (langsung, dalam perangkat keras) lampu. , atau sedikit dalam memori yang memberi tahu Anda apakah tombol keyboard turun atau tidak (karena terhubung oleh perangkat keras langsung ke tombol).
Tampaknya "gunakan 1" tidak terjadi dalam kode portabel yang targetnya mencakup sistem multi-core.
PENGGUNAAN 2
Tidak jauh berbeda dari "penggunaan 1" adalah memori yang dapat dibaca atau ditulis kapan saja oleh penangan interupsi (yang mungkin mengontrol lampu atau menyimpan info dari kunci). Tetapi sudah untuk ini kita memiliki masalah yang tergantung pada sistem, penangan interrupt mungkin berjalan pada inti yang berbeda dengan cache memori sendiri , dan "volatile" tidak menjamin koherensi cache pada semua sistem.
Jadi "use 2" tampaknya melampaui apa yang "volatile" dapat berikan.
GUNAKAN 3
Satu-satunya penggunaan tak terbantahkan lainnya yang saya lihat adalah untuk mencegah kesalahan optimasi akses melalui variabel yang berbeda yang menunjuk ke memori yang sama yang tidak disadari oleh kompiler adalah memori yang sama. Tetapi ini mungkin hanya tidak perlu dipermasalahkan karena orang tidak membicarakannya - saya hanya melihat satu menyebutkannya. Dan saya pikir standar C sudah mengakui bahwa pointer "berbeda" (seperti argumen yang berbeda untuk suatu fungsi) mungkin menunjuk ke item yang sama atau item terdekat, dan sudah menentukan bahwa kompiler harus menghasilkan kode yang berfungsi bahkan dalam kasus seperti itu. Namun, saya tidak dapat dengan cepat menemukan topik ini dalam standar (500 halaman!) Terbaru.
Jadi "gunakan 3" mungkin tidak ada sama sekali?
Karena itu pertanyaan saya:
Apakah "volatile" menjamin semuanya dalam kode C portabel untuk sistem multi-core?
EDIT - perbarui
Setelah browsing standar terbaru , sepertinya jawabannya paling tidak ya sangat terbatas:
1. Standar berulang kali menentukan perlakuan khusus untuk tipe spesifik "volatile sig_atomic_t". Namun standar juga mengatakan bahwa penggunaan fungsi sinyal dalam program multi-threaded menghasilkan perilaku yang tidak terdefinisi. Jadi use case ini tampaknya terbatas pada komunikasi antara program single-threaded dan pengendali sinyal.
2. Standar juga menentukan arti yang jelas untuk "volatile" dalam kaitannya dengan setjmp / longjmp. (Kode contoh yang penting diberikan dalam pertanyaan dan jawaban lain .)
Jadi pertanyaan yang lebih tepat adalah:
Apakah "volatile" menjamin semuanya dalam kode C portabel untuk sistem multi-inti, selain dari (1) memungkinkan program berulir tunggal untuk menerima informasi dari pengendali sinyal, atau (2) memungkinkan setjmp kode untuk melihat variabel yang dimodifikasi antara setjmp dan longjmp?
Ini masih merupakan pertanyaan ya / tidak.
Jika "ya", alangkah baiknya jika Anda dapat menunjukkan contoh kode portabel bebas bug yang menjadi bermasalah jika "tidak stabil" dihilangkan. Jika "tidak", maka saya kira kompiler bebas untuk mengabaikan "volatile" di luar dua kasus yang sangat spesifik ini, untuk target multi-core.
volatile
menginformasikan program bahwa itu dapat berubah secara tidak sinkron.volatile
spesifik, yang saya percaya perlu.Jawaban:
Tidak, sama sekali tidak . Dan itu membuat volatile hampir tidak berguna untuk tujuan kode aman MT.
Jika ya, maka volatile akan cukup baik untuk variabel yang dibagikan oleh banyak utas karena memesan peristiwa dalam cache L1 adalah semua yang perlu Anda lakukan dalam CPU biasa (baik multi-core atau multi-CPU pada motherboard) yang mampu bekerja sama dengan cara yang memungkinkan implementasi normal dari C / C ++ atau Java multithreading mungkin dengan biaya yang diharapkan biasa (yaitu, bukan biaya besar pada sebagian besar operasi mutasi atom atau tidak puas).
Tetapi volatile tidak memberikan pemesanan yang dijamin (atau "visibilitas memori") dalam cache baik secara teori maupun dalam praktik.
(Catatan: berikut ini didasarkan pada interpretasi yang kuat dari dokumen standar, maksud standar, praktik sejarah, dan pemahaman mendalam tentang harapan penulis kompiler. Pendekatan ini didasarkan pada sejarah, praktik aktual, dan harapan dan pemahaman orang nyata di dunia nyata, yang jauh lebih kuat dan lebih dapat diandalkan daripada parsing kata-kata dokumen yang tidak dikenal sebagai penulisan spesifikasi bintang dan yang telah direvisi berkali-kali.)
Dalam praktiknya, volatile memang menjamin kemampuan ptrace yaitu kemampuan untuk menggunakan informasi debug untuk program yang sedang berjalan, pada tingkat optimasi apa pun , dan fakta bahwa informasi debug masuk akal untuk objek volatil ini:
ptrace
(mekanisme seperti ptrace) untuk mengatur titik break yang berarti pada titik-titik urutan setelah operasi yang melibatkan objek volatil: Anda benar-benar dapat menembus tepat pada titik-titik ini (perhatikan bahwa ini hanya berfungsi jika Anda bersedia untuk menetapkan banyak titik break seperti halnya Pernyataan C / C ++ dapat dikompilasi ke banyak titik awal dan akhir perakitan yang berbeda, seperti dalam loop yang tidak dibuka secara masif);Jaminan yang mudah menguap dalam praktiknya sedikit lebih dari interpretasi ptrace yang ketat: ia juga menjamin bahwa variabel otomatis yang tidak stabil memiliki alamat pada stack, karena mereka tidak dialokasikan untuk register, alokasi register yang akan membuat manipulasi ptrace lebih halus (kompiler dapat output informasi debug untuk menjelaskan bagaimana variabel dialokasikan ke register, tetapi membaca dan mengubah status register sedikit lebih terlibat daripada mengakses alamat memori).
Perhatikan bahwa kemampuan debug penuh program, yang mempertimbangkan semua variabel volatile setidaknya pada titik-titik urutan, disediakan oleh mode "optimisasi nol" dari kompiler, mode yang masih melakukan optimasi sepele seperti penyederhanaan aritmatika (biasanya tidak ada jaminan tidak ada optimasi di semua mode). Tetapi volatile lebih kuat daripada non optimasi:
x-x
dapat disederhanakan untuk integer non volatilex
tetapi tidak untuk objek volatile.Jadi volatile berarti dijamin akan dikompilasi sebagaimana adanya , seperti terjemahan dari sumber ke biner / rakitan oleh kompiler panggilan sistem bukanlah reinterpretasi, diubah, atau dioptimalkan dengan cara apa pun oleh kompiler. Perhatikan bahwa panggilan perpustakaan mungkin atau mungkin bukan panggilan sistem. Banyak fungsi sistem resmi sebenarnya adalah fungsi pustaka yang menawarkan lapisan interposisi yang tipis dan umumnya tunduk pada kernel pada akhirnya. (Khususnya
getpid
tidak perlu pergi ke kernel dan bisa membaca lokasi memori yang disediakan oleh OS yang berisi informasi tersebut.)Interaksi yang mudah menguap adalah interaksi dengan dunia luar dari mesin nyata , yang harus mengikuti "mesin abstrak". Mereka bukan interaksi internal bagian-bagian program dengan bagian-bagian program lainnya. Compiler hanya dapat memberikan alasan tentang apa yang diketahuinya, yaitu bagian-bagian program internal.
Pembuatan kode untuk akses yang mudah menguap harus mengikuti interaksi paling alami dengan lokasi memori itu: harus tidak mengejutkan. Itu berarti bahwa beberapa akses yang mudah menguap diharapkan menjadi atom : jika cara alami untuk membaca atau menulis representasi dari
long
pada arsitektur adalah atom, maka diharapkan membaca atau menulisvolatile long
akan berupa atom, karena kompiler seharusnya tidak menghasilkan kode tidak efisien konyol untuk mengakses objek volatile byte demi byte, misalnya .Anda harus dapat menentukan itu dengan mengetahui arsitekturnya. Anda tidak perlu tahu apa-apa tentang kompiler, karena volatile berarti bahwa kompiler harus transparan .
Tetapi volatile tidak lebih dari memaksa emisi perakitan yang diharapkan untuk kasus yang paling tidak dioptimalkan untuk kasus-kasus tertentu untuk melakukan operasi memori: semantik volatile berarti semantik kasus umum.
Kasus umum adalah apa yang dilakukan kompiler ketika tidak memiliki informasi tentang konstruk: f.ex. memanggil fungsi virtual pada nilai melalui pengiriman dinamis adalah kasus umum, membuat panggilan langsung ke overrider setelah menentukan pada waktu kompilasi jenis objek yang ditunjuk oleh ekspresi adalah kasus tertentu. Kompiler selalu memiliki penanganan kasus umum dari semua konstruksi, dan mengikuti ABI.
Volatile tidak melakukan hal khusus untuk menyinkronkan utas atau menyediakan "visibilitas memori": volatile hanya memberikan jaminan pada tingkat abstrak yang terlihat dari dalam sebuah thread yang mengeksekusi atau menghentikan, yaitu bagian dalam inti CPU :
Hanya poin kedua yang berarti volatile tidak berguna di sebagian besar masalah komunikasi antar utas; poin pertama pada dasarnya tidak relevan dalam masalah pemrograman yang tidak melibatkan komunikasi dengan komponen perangkat keras di luar CPU (s) tetapi masih di bus memori.
Properti volatile yang menyediakan perilaku terjamin dari sudut pandang inti yang menjalankan utas berarti bahwa sinyal asinkron dikirim ke utas itu, yang dijalankan dari sudut pandang urutan pelaksanaan utas tersebut, lihat operasi dalam urutan kode sumber .
Kecuali jika Anda berencana untuk mengirim sinyal ke utas Anda (suatu pendekatan yang sangat berguna untuk konsolidasi informasi tentang utas yang sedang berjalan tanpa titik penghentian yang disepakati sebelumnya), volatil bukan untuk Anda.
sumber
Saya bukan ahli, tetapi cppreference.com memberi saya informasi yang
volatile
cukup bagus . Inilah intinya:Ini juga memberikan beberapa kegunaan:
Dan tentu saja, itu menyebutkan yang
volatile
tidak berguna untuk sinkronisasi utas:sumber
longjmp
kode C ++.Pertama-tama, secara historis ada banyak cegukan sehubungan dengan beragam interpretasi makna
volatile
akses dan sejenisnya. Lihat studi ini: Volatile Disalahgunakan, dan Apa yang Harus Dilakukan tentang Itu .Terlepas dari berbagai masalah yang disebutkan dalam penelitian itu, perilaku
volatile
portabel, simpan untuk satu aspek dari mereka: ketika mereka bertindak sebagai hambatan memori . Penghalang memori adalah beberapa mekanisme yang ada untuk mencegah eksekusi kode Anda yang tidak dilakukan secara bersamaan. Menggunakanvolatile
sebagai penghalang memori tentu tidak portabel.Apakah bahasa C menjamin perilaku memori atau tidak dari
volatile
itu tampaknya bisa diperdebatkan, meskipun secara pribadi saya pikir bahasa itu jelas. Pertama kita memiliki definisi formal tentang efek samping, C17 5.1.2.3:Standar mendefinisikan urutan sekuensing, sebagai cara menentukan urutan evaluasi (eksekusi). Definisi ini formal dan rumit:
TL; DR di atas pada dasarnya adalah jika kita memiliki ekspresi
A
yang mengandung efek samping, itu harus dilakukan mengeksekusi sebelum ekspresi lainB
, dalam kasusB
diurutkan setelahnyaA
.Optimalisasi kode C dimungkinkan melalui bagian ini:
Ini berarti bahwa program dapat mengevaluasi (mengeksekusi) ekspresi dalam urutan yang standar mandat di tempat lain (urutan evaluasi, dll). Tetapi itu tidak perlu mengevaluasi (mengeksekusi) suatu nilai jika dapat menyimpulkan bahwa itu tidak digunakan. Misalnya, operasi
0 * x
tidak perlu mengevaluasix
dan hanya mengganti ekspresi dengan0
.Kecuali mengakses variabel adalah efek samping. Artinya dalam hal
x
inivolatile
, ia harus mengevaluasi (mengeksekusi)0 * x
meskipun hasilnya akan selalu 0. Optimasi tidak diperbolehkan.Selain itu, standar tersebut berbicara tentang perilaku yang dapat diamati:
Mengingat semua hal di atas, implementasi yang sesuai (compiler + sistem yang mendasarinya) tidak dapat mengeksekusi akses
volatile
objek dalam urutan yang tidak ditentukan, jika semantik dari sumber C tertulis mengatakan sebaliknya.Ini berarti bahwa dalam contoh ini
Kedua ekspresi penugasan harus dievaluasi dan
z = x;
harus dievaluasi sebelumnyaz = y;
. Implementasi multi-prosesor yang meng-outsource dua operasi ini ke dua core unecessence yang berbeda tidak sesuai!Dilema adalah bahwa kompiler tidak dapat berbuat banyak tentang hal-hal seperti pra-pengambilan caching dan instruksi pipelining dll, terutama tidak ketika berjalan di atas OS. Jadi kompiler menyerahkan masalah itu kepada programmer, memberi tahu mereka bahwa hambatan memori sekarang menjadi tanggung jawab programmer. Sementara standar C dengan jelas menyatakan bahwa masalah perlu diselesaikan oleh kompiler.
Kompiler tidak perlu peduli untuk menyelesaikan masalah, dan karenanya
volatile
bertindak sebagai penghalang memori adalah non-portabel. Ini telah menjadi masalah kualitas implementasi.sumber
z
harus benar-benar dieksekusi? (sepertiz = x; z = y;
) Nilainya akan dihapus dalam pernyataan berikutnya.z
benar - benar ditugaskan dua kali? Bagaimana Anda tahu bahwa "membaca dieksekusi"?