Mengapa volatile ada?


222

Apa yang dilakukan volatilekata kunci? Dalam C ++ masalah apa yang dipecahkan?

Dalam kasus saya, saya tidak pernah secara sengaja membutuhkannya.


Berikut ini adalah diskusi yang menarik tentang volatile sehubungan dengan pola Singleton: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
chessguy

3
Ada teknik menarik yang membuat kompiler Anda mendeteksi kemungkinan kondisi balapan yang sangat bergantung pada kata kunci yang mudah menguap, Anda dapat membacanya di http://www.ddj.com/cpp/184403766 .
Neno Ganchev

Ini adalah sumber yang bagus dengan contoh kapan volatilebisa digunakan secara efektif, disatukan dalam istilah awam yang cantik. Tautan: publications.gbdirect.co.uk/c_book/chapter8/…
Dioptimalkan Coder

Saya menggunakannya untuk mengunci kode bebas / penguncian ganda diperiksa
paulm

Bagi saya, volatilelebih bermanfaat daripada friendkata kunci.
acegs

Jawaban:


268

volatile diperlukan jika Anda membaca dari satu titik di memori yang, katakanlah, proses / perangkat yang sepenuhnya terpisah / apa pun yang dapat dituliskan.

Saya dulu bekerja dengan ram dual-port dalam sistem multiprosesor dalam lurus C. Kami menggunakan perangkat keras yang dikelola nilai 16 bit sebagai semaphore untuk mengetahui kapan orang lain selesai. Pada dasarnya kami melakukan ini:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Tanpa volatile, pengoptimal melihat loop sebagai tidak berguna (Orang itu tidak pernah menetapkan nilai! Dia gila, singkirkan kode itu!) Dan kode saya akan melanjutkan tanpa memperoleh semaphore, menyebabkan masalah di kemudian hari.


Dalam hal ini, apa yang akan terjadi jika uint16_t* volatile semPtrditulis sebagai gantinya? Ini harus menandai pointer sebagai volatile (bukan nilai yang ditunjukkan), sehingga memeriksa ke pointer itu sendiri, misalnya semPtr == SOME_ADDRmungkin tidak dioptimalkan. Namun ini menyiratkan nilai runcing volatile lagi juga. Tidak?
Zyl

@Zyl Tidak, tidak. Dalam praktiknya, apa yang Anda sarankan kemungkinan akan terjadi. Tetapi secara teoritis, seseorang dapat berakhir dengan kompiler yang mengoptimalkan akses ke nilai karena memutuskan bahwa tidak ada nilai-nilai yang pernah berubah. Dan jika Anda bermaksud volatile untuk menerapkan nilai dan bukan pointer, Anda akan gagal. Sekali lagi, tidak mungkin, tetapi lebih baik melakukan kesalahan dengan melakukan hal yang benar, daripada mengambil keuntungan dari perilaku yang terjadi pada pekerjaan hari ini.
iheanyi

1
@Doug T. Penjelasan yang lebih baik adalah ini
machineaddict

3
@ curiousguy itu tidak salah memutuskan. Itu membuat pengurangan yang benar berdasarkan informasi yang diberikan. Jika Anda gagal menandai sesuatu yang fluktuatif, kompiler bebas untuk menganggap bahwa itu tidak stabil . Itulah yang dilakukan kompiler ketika mengoptimalkan kode. Jika ada lebih banyak informasi, yaitu, bahwa data tersebut ternyata volatile, itu tanggung jawab programmer untuk memberikan informasi itu. Apa yang Anda klaim oleh kompiler buggy adalah pemrograman yang benar-benar buruk.
iheanyi

1
@curiousguy tidak, hanya karena kata kunci yang mudah menguap muncul sekali tidak berarti semuanya tiba-tiba menjadi tidak stabil. Saya memberikan skenario di mana kompiler melakukan hal yang benar dan mencapai hasil yang bertentangan dengan apa yang diharapkan oleh programmer. Sama seperti "parsing yang paling menjengkelkan" bukanlah tanda kesalahan kompiler, demikian juga halnya di sini.
iheanyi

82

volatilediperlukan saat mengembangkan sistem tertanam atau driver perangkat, di mana Anda perlu membaca atau menulis perangkat keras yang dipetakan memori. Isi register perangkat tertentu dapat berubah kapan saja, jadi Anda perlu volatilekata kunci untuk memastikan bahwa akses tersebut tidak dioptimalkan oleh kompiler.


9
Ini tidak hanya berlaku untuk sistem tertanam tetapi untuk semua pengembangan driver perangkat.
Mladen Janković

Satu-satunya waktu saya pernah membutuhkannya pada bus ISA 8 bit di mana Anda membaca alamat yang sama dua kali - kompiler memiliki bug dan mengabaikannya (awal Zortech c ++)
Martin Beckett

Volatile sangat jarang memadai untuk mengontrol perangkat eksternal. Semantiknya salah untuk MMIO modern: Anda harus membuat terlalu banyak objek yang mudah menguap dan merusak optimasi. Tetapi MMIO modern berperilaku seperti memori normal sampai flag diatur sehingga volatile tidak diperlukan. Banyak driver tidak pernah menggunakan volatile.
curiousguy

69

Beberapa prosesor memiliki register titik apung yang memiliki lebih dari 64 bit presisi (mis. 32-bit x86 tanpa SSE, lihat komentar Peter). Dengan begitu, jika Anda menjalankan beberapa operasi pada angka presisi ganda, Anda benar-benar mendapatkan jawaban dengan presisi lebih tinggi daripada jika Anda memotong setiap hasil antara hingga 64 bit.

Ini biasanya bagus, tetapi itu berarti bahwa tergantung pada bagaimana kompiler ditugaskan register dan melakukan optimasi Anda akan memiliki hasil yang berbeda untuk operasi yang sama persis pada input yang sama persis. Jika Anda memerlukan konsistensi maka Anda dapat memaksa setiap operasi untuk kembali ke memori dengan menggunakan kata kunci yang mudah menguap.

Ini juga berguna untuk beberapa algoritma yang tidak masuk akal aljabar tetapi mengurangi kesalahan floating point, seperti penjumlahan Kahan. Secara aljabar itu nop, jadi sering kali akan salah dioptimalkan keluar kecuali beberapa variabel menengah yang mudah menguap.


5
Ketika Anda menghitung turunan numerik, itu berguna juga, untuk memastikan x + h - x == h Anda mendefinisikan hh = x + h - x sebagai volatile sehingga delta yang tepat dapat dihitung.
Alexandre C.

5
+1, memang dalam pengalaman saya ada kasus ketika perhitungan floating-point menghasilkan hasil yang berbeda di Debug dan Rilis, jadi unit test yang ditulis untuk satu konfigurasi gagal untuk yang lain. Kami menyelesaikannya dengan mendeklarasikan satu variabel floating-point sebagai volatile doublebukan hanya double, jadi untuk memastikan bahwa variabel tersebut terpotong dari presisi FPU ke presisi 64-bit (RAM) sebelum melanjutkan perhitungan lebih lanjut. Hasilnya secara substansial berbeda karena berlebihan kesalahan floating-point.
Serge Rogatch

Definisi Anda tentang "modern" agak keliru. Hanya kode x86 32-bit yang menghindari SSE / SSE2 dipengaruhi oleh ini, dan itu bukan "modern" bahkan 10 tahun yang lalu. MIPS / ARM / POWER semua memiliki register perangkat keras 64-bit, dan begitu pula x86 dengan SSE2. Implementasi C ++ x86-64 selalu menggunakan SSE2, dan kompiler memiliki opsi ingin g++ -mfpmath=ssemenggunakannya untuk x86 32-bit juga. Anda dapat menggunakan gcc -ffloat-storeuntuk memaksa pembulatan ke mana - mana bahkan saat menggunakan x87, atau Anda dapat mengatur ketepatan x87 menjadi mantissa 53-bit: randomascii.wordpress.com/2012/03/21/… .
Peter Cordes

Tetapi jawaban yang masih bagus, untuk kode-gen x87 usang, Anda dapat menggunakannya volatileuntuk memaksa pembulatan di beberapa tempat tertentu tanpa kehilangan manfaat di mana-mana.
Peter Cordes

1
Atau apakah saya bingung dengan ketidakkonsistenan?
Chipster

49

Dari artikel "Volatile as a berjanji" oleh Dan Saks:

(...) objek yang mudah menguap adalah benda yang nilainya mungkin berubah secara spontan. Artinya, ketika Anda mendeklarasikan objek menjadi volatil, Anda memberi tahu kompiler bahwa objek tersebut dapat berubah status meskipun tidak ada pernyataan dalam program yang tampaknya mengubahnya. "

Berikut ini tautan ke tiga artikelnya mengenai volatilekata kunci:


23

Anda HARUS menggunakan volatile saat menerapkan struktur data bebas kunci. Kalau tidak, kompiler bebas untuk mengoptimalkan akses ke variabel, yang akan mengubah semantik.

Dengan kata lain, volatile memberi tahu kompiler bahwa akses ke variabel ini harus sesuai dengan operasi baca / tulis memori fisik.

Misalnya, ini adalah bagaimana InterlockedIncrement dideklarasikan di Win32 API:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

Anda benar-benar TIDAK perlu mendeklarasikan volatile variabel untuk dapat menggunakan InterlockedIncrement.
curiousguy

Jawaban ini sudah usang sekarang karena C ++ 11 menyediakan std::atomic<LONG>sehingga Anda dapat menulis kode tanpa kunci lebih aman tanpa masalah memiliki beban murni / toko murni dioptimalkan, atau dipesan ulang, atau apa pun.
Peter Cordes

10

Sebuah aplikasi besar yang saya gunakan untuk bekerja pada awal 1990-an berisi penanganan pengecualian berbasis C menggunakan setjmp dan longjmp. Kata kunci yang mudah menguap diperlukan pada variabel yang nilainya perlu dipertahankan dalam blok kode yang berfungsi sebagai klausa "catch", agar vars tersebut tidak disimpan dalam register dan dihilangkan oleh longjmp.


10

Dalam Standar C, salah satu tempat untuk digunakan volatileadalah dengan penangan sinyal. Bahkan, dalam Standar C, semua yang dapat Anda lakukan dengan aman dalam penangan sinyal adalah memodifikasi volatile sig_atomic_tvariabel, atau keluar dengan cepat. Memang, AFAIK, itu adalah satu-satunya tempat dalam Standar C yang volatilediperlukan untuk menghindari perilaku yang tidak terdefinisi.

ISO / IEC 9899: 2011 §7.14.1.1 signalFungsi

¶5 Jika sinyal muncul selain dari hasil pemanggilan fungsi abortatau raise, perilaku tidak terdefinisi jika penangan sinyal mengacu pada objek apa pun dengan durasi penyimpanan statis atau benang yang bukan objek atom bebas kunci selain dengan menetapkan nilai ke objek yang dideklarasikan sebagai volatile sig_atomic_t, atau pengendali sinyal memanggil fungsi apa pun di pustaka standar selain abortfungsi, _Exitfungsi, quick_exitfungsi, atau signalfungsi dengan argumen pertama sama dengan nomor sinyal yang sesuai dengan sinyal yang menyebabkan permohonan doa pawang Selain itu, jika panggilan ke signalfungsi menghasilkan pengembalian SIG_ERR, nilai errnotidak pasti. 252)

252) Jika ada sinyal yang dihasilkan oleh penangan sinyal asinkron, perilaku tidak terdefinisi.

Itu berarti bahwa dalam Standar C, Anda dapat menulis:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

dan tidak banyak lagi.

POSIX jauh lebih lunak tentang apa yang dapat Anda lakukan dalam penangan sinyal, tetapi masih ada batasan (dan salah satu batasannya adalah bahwa pustaka I / O Standar - printf()et al - tidak dapat digunakan dengan aman).


7

Mengembangkan untuk tertanam, saya memiliki loop yang memeriksa variabel yang dapat diubah dalam penangan interrupt. Tanpa "volatile", loop menjadi noop - sejauh yang dapat dikompilasi oleh kompiler, variabel tidak pernah berubah, sehingga mengoptimalkan pemeriksaan.

Hal yang sama akan berlaku untuk variabel yang dapat diubah di utas berbeda di lingkungan yang lebih tradisional, tetapi di sana kita sering melakukan panggilan sinkronisasi, jadi kompiler tidak begitu bebas dengan optimasi.


7

Saya telah menggunakannya dalam membangun debug ketika kompiler bersikeras mengoptimalkan variabel yang saya ingin dapat melihat ketika saya melangkah melalui kode.


7

Selain menggunakannya sebagaimana dimaksud, volatile digunakan dalam metaprogramming (templat). Itu dapat digunakan untuk mencegah overloading yang tidak disengaja, karena atribut volatile (seperti const) mengambil bagian dalam resolusi overload.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

Ini legal; keduanya kelebihan berpotensi dipanggil dan melakukan hampir sama. Para pemain di volatileoverload adalah legal karena kita tahu bar tidak akan lulus non-volatile T. The volatileversi ketat buruk, meskipun, jadi tidak pernah dipilih dalam resolusi yang berlebihan jika non-volatile ftersedia.

Perhatikan bahwa kode tidak pernah benar-benar tergantung pada volatileakses memori.


Bisakah Anda menguraikan ini dengan sebuah contoh? Itu benar-benar membantu saya memahami lebih baik. Terima kasih!
batbrat

" Para pemain di volatile overload " Para pemain adalah konversi eksplisit. Ini adalah konstruksi SYNTAX. Banyak orang membuat kebingungan itu (bahkan penulis standar).
curiousguy

6
  1. Anda harus menggunakannya untuk mengimplementasikan spinlocks serta beberapa (semua?) struktur data bebas kunci
  2. gunakan dengan operasi atom / instruksi
  3. membantu saya sekali untuk mengatasi bug kompiler (kode yang dihasilkan salah selama optimasi)

5
Anda lebih baik menggunakan perpustakaan, kompiler intrinsik, atau kode perakitan inline. Volatile tidak bisa diandalkan.
Zan Lynx

1
1 dan 2 keduanya menggunakan operasi atom, tetapi volatile tidak memberikan semantik atom dan implementasi atom platform-spesifik akan menggantikan kebutuhan untuk menggunakan volatile, jadi untuk 1 dan 2, saya tidak setuju, Anda TIDAK perlu volatile untuk ini.

Siapa bilang apa-apa tentang semantik atom menyediakan volatile? Saya mengatakan Anda perlu MENGGUNAKAN volatile DENGAN operasi atom dan jika Anda tidak berpikir itu benar melihat deklarasi operasi yang saling terkait dari win32 API (orang ini juga menjelaskan ini dalam jawabannya)
Mladen Janković

4

Itu volatile kunci dimaksudkan untuk mencegah kompiler menerapkan optimasi pada objek yang dapat berubah dengan cara yang tidak dapat ditentukan oleh kompiler.

Objek yang dinyatakan sebagai volatiledihilangkan dari optimasi karena nilainya dapat diubah dengan kode di luar lingkup kode saat ini setiap saat. Sistem selalu membaca nilai saat ini dari avolatile objek dari lokasi memori daripada menyimpan nilainya dalam register sementara pada titik yang diminta, bahkan jika instruksi sebelumnya meminta nilai dari objek yang sama.

Pertimbangkan kasus-kasus berikut

1) Variabel global yang dimodifikasi oleh rutinitas layanan interupsi di luar ruang lingkup.

2) Variabel global dalam aplikasi multi-utas.

Jika kami tidak menggunakan kualifikasi volatil, masalah berikut mungkin muncul

1) Kode mungkin tidak berfungsi seperti yang diharapkan ketika optimisasi dihidupkan.

2) Kode mungkin tidak berfungsi seperti yang diharapkan ketika interupsi diaktifkan dan digunakan.

Volatile: Sahabat seorang programmer

https://en.wikipedia.org/wiki/Volatile_(computer_programming)


Tautan yang Anda poskan sangat usang dan tidak mencerminkan praktik terbaik saat ini.
Tim Seguine

2

Selain fakta bahwa kata kunci yang mudah menguap digunakan untuk memberi tahu kompiler untuk tidak mengoptimalkan akses ke beberapa variabel (yang dapat dimodifikasi oleh utas atau rutin interupsi), kata kunci juga dapat digunakan untuk menghapus beberapa bug penyusun - YA dapat menjadi ---.

Sebagai contoh saya bekerja pada platform yang tertanam adalah kompiler membuat beberapa asumsi yang salah mengenai nilai suatu variabel. Jika kode tidak dioptimalkan, program akan berjalan ok. Dengan optimasi (yang benar-benar diperlukan karena itu adalah rutinitas kritis) kode tidak akan berfungsi dengan benar. Satu-satunya solusi (meskipun tidak terlalu benar) adalah mendeklarasikan variabel 'salah' sebagai volatile.


3
Ini adalah asumsi yang salah bahwa kompiler tidak mengoptimalkan akses ke volatile. Standar tidak tahu apa-apa tentang pengoptimalan. Compiler diharuskan untuk menghormati apa yang ditentukan oleh standar, tetapi bebas untuk melakukan optimasi apa pun yang tidak mengganggu perilaku normal.
Terminus

3
Dari pengalaman saya 99,9% dari semua "bug" optimasi di lengan gcc adalah kesalahan pada bagian programmer. Tidak tahu apakah ini berlaku untuk jawaban ini. Hanya kata-kata kasar pada topik umum
odinthenerd

@Terminus " Ini adalah asumsi yang salah bahwa kompiler tidak mengoptimalkan akses ke volatile " Sumber?
curiousguy

2

Program Anda tampaknya berfungsi bahkan tanpa volatilekata kunci? Mungkin inilah alasannya:

Seperti disebutkan sebelumnya volatilekata kunci membantu untuk kasus seperti

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

Tetapi tampaknya hampir tidak ada efek begitu fungsi eksternal atau non-inline dipanggil. Misalnya:

while( *p!=0 ) { g(); }

Kemudian dengan atau tanpa hasil yang volatilehampir sama dihasilkan.

Selama g () dapat sepenuhnya inline, kompiler dapat melihat semua yang terjadi dan karenanya dapat mengoptimalkan. Tetapi ketika program membuat panggilan ke tempat di mana kompiler tidak dapat melihat apa yang terjadi, tidak aman bagi kompiler untuk membuat asumsi lagi. Karenanya kompiler akan menghasilkan kode yang selalu membaca dari memori secara langsung.

Namun waspadalah pada hari itu, ketika fungsi Anda g () menjadi sebaris (baik karena perubahan eksplisit atau karena kepintaran kompiler / linker) maka kode Anda mungkin rusak jika Anda lupa volatilekata kunci!

Karena itu saya sarankan untuk menambahkan volatilekata kunci bahkan jika program Anda tampaknya berfungsi tanpa. Itu membuat niat lebih jelas dan lebih kuat dalam hal perubahan di masa depan.


Perhatikan bahwa suatu fungsi dapat memiliki kode yang diuraikan sementara masih menghasilkan referensi (diselesaikan pada waktu tautan) ke fungsi garis besar; ini akan menjadi kasus fungsi rekursif sebagian inline. Suatu fungsi juga dapat memiliki semantik "inline" oleh kompiler, yaitu kompiler mengasumsikan efek samping dan hasilnya berada dalam efek samping yang mungkin dan hasil yang mungkin sesuai dengan kode sumbernya, sementara masih tidak menggarisbawahinya. Ini didasarkan pada "Aturan Satu Definisi yang efektif" yang menyatakan bahwa semua definisi suatu entitas harus setara secara efektif (jika tidak persis sama).
curiousguy

Menghindari penggambaran panggilan (atau "inlining" semantiknya dengan portable) oleh fungsi yang badannya dapat dilihat oleh kompiler (bahkan pada waktu link dengan optimasi global) dimungkinkan dengan menggunakan volatilepenunjuk fungsi yang berkualitas:void (* volatile fun_ptr)() = fun; fun_ptr();
curiousguy

2

Pada hari-hari awal C, kompiler akan menafsirkan semua tindakan yang membaca dan menulis nilai-nilai sebagai operasi memori, yang harus dilakukan dalam urutan yang sama dengan membaca dan menulis muncul dalam kode. Efisiensi dapat sangat ditingkatkan dalam banyak kasus jika penyusun diberi sejumlah kebebasan untuk memesan ulang dan mengkonsolidasikan operasi, tetapi ada masalah dengan ini. Bahkan operasi sering ditentukan dalam urutan tertentu hanya karena itu perlu untuk menentukannya dalam urutan tertentu , dan dengan demikian programmer memilih salah satu dari banyak alternatif yang sama baiknya, itu tidak selalu terjadi. Terkadang penting bahwa operasi tertentu terjadi dalam urutan tertentu.

Rincian urutan mana yang penting akan bervariasi tergantung pada platform target dan bidang aplikasi. Alih-alih memberikan kontrol yang terinci, Standar memilih model sederhana: jika urutan akses dilakukan dengan nilai-nilai yang tidak memenuhi syarat volatile, kompiler dapat menyusun ulang dan mengkonsolidasikannya sesuai keinginan. Jika suatu tindakan dilakukan dengan nilai- volatilewajar terkualifikasi, implementasi kualitas harus menawarkan jaminan pemesanan tambahan apa pun yang mungkin diperlukan oleh kode yang menargetkan platform dan bidang aplikasi yang dimaksud, tanpa harus menggunakan sintaksis non-standar.

Sayangnya, daripada mengidentifikasi jaminan apa yang dibutuhkan oleh programmer, banyak penyusun memilih untuk menawarkan jaminan minimum yang diamanatkan oleh Standar. Ini membuat volatilejauh lebih bermanfaat daripada yang seharusnya. Pada gcc atau dentang, misalnya, seorang programmer perlu menerapkan dasar "hand-off mutex" [tugas di mana tugas yang telah memperoleh dan merilis suatu mutex tidak akan melakukannya lagi sampai tugas lain selesai] harus melakukan satu dari empat hal:

  1. Masukkan akuisisi dan pelepasan mutex ke dalam fungsi yang kompilernya tidak bisa sebaris, dan yang tidak bisa diterapkan Seluruh Program Optimasi.

  2. Kualifikasi semua objek yang dijaga oleh mutex sebagai - volatilesesuatu yang seharusnya tidak perlu jika semua akses terjadi setelah memperoleh mutex dan sebelum melepaskannya.

  3. Gunakan optimasi level 0 untuk memaksa compiler untuk menghasilkan kode seolah-olah semua benda yang tidak berkualitas registeryang volatile.

  4. Gunakan arahan khusus gcc.

Sebaliknya, ketika menggunakan kompiler berkualitas lebih tinggi yang lebih cocok untuk pemrograman sistem, seperti icc, seseorang akan memiliki opsi lain:

  1. Pastikan bahwa volatilepenulisan yang berkualifikasi dilakukan di semua tempat untuk memperoleh atau merilis.

Mengakuisisi "hand-off mutex" dasar memerlukan volatilepembacaan (untuk melihat apakah sudah siap), dan seharusnya tidak memerlukan volatilepenulisan juga (pihak lain tidak akan mencoba untuk mendapatkannya kembali sampai dikembalikan) tetapi harus melakukan volatilepenulisan yang tidak berarti masih lebih baik daripada opsi apa pun yang tersedia di bawah gcc atau dentang.


1

Satu penggunaan yang harus saya ingatkan adalah dalam fungsi pengendali sinyal, jika Anda ingin mengakses / memodifikasi variabel global (misalnya, tandai sebagai exit = true) Anda harus mendeklarasikan variabel itu sebagai 'volatil'.


1

Semua jawaban sangat bagus. Tetapi di atas itu, saya ingin membagikan sebuah contoh.

Di bawah ini adalah program cpp kecil:

#include <iostream>

int x;

int main(){
    char buf[50];
    x = 8;

    if(x == 8)
        printf("x is 8\n");
    else
        sprintf(buf, "x is not 8\n");

    x=1000;
    while(x > 5)
        x--;
    return 0;
}

Sekarang, mari kita hasilkan perakitan kode di atas (dan saya hanya akan menempelkan bagian-bagian dari perakitan yang relevan di sini):

Perintah untuk menghasilkan perakitan:

g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp

Dan majelis:

main:
.LFB1594:
    subq    $40, %rsp    #,
    .seh_stackalloc 40
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC0(%rip), %rcx     #,
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:10:         printf("x is 8\n");
    call    _ZL6printfPKcz.constprop.0   #
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    movl    $5, x(%rip)  #, x
    addq    $40, %rsp    #,
    ret 
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

Anda dapat melihat dalam rakitan bahwa kode rakitan tidak dibuat sprintfkarena kompiler berasumsi bahwa xtidak akan berubah di luar program. Dan sama halnya dengan whileloop. whileloop sama sekali dihapus karena optimasi karena kompilator melihatnya sebagai kode yang tidak berguna dan dengan demikian langsung ditugaskan 5ke x(lihat movl $5, x(%rip)).

Masalahnya terjadi ketika bagaimana jika proses / perangkat keras eksternal akan mengubah nilai xsuatu tempat antara x = 8;dan if(x == 8). Kami berharap elseblok berfungsi, tetapi sayangnya kompiler telah memangkas bagian itu.

Sekarang, untuk menyelesaikan ini, di assembly.cpp , mari kita ubah int x;untuk volatile int x;dan cepat melihat kode assembly yang dihasilkan:

main:
.LFB1594:
    subq    $104, %rsp   #,
    .seh_stackalloc 104
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:9:     if(x == 8)
    movl    x(%rip), %eax    # x, x.1_1
 # assembly.cpp:9:     if(x == 8)
    cmpl    $8, %eax     #, x.1_1
    je  .L11     #,
 # assembly.cpp:12:         sprintf(buf, "x is not 8\n");
    leaq    32(%rsp), %rcx   #, tmp93
    leaq    .LC0(%rip), %rdx     #,
    call    _ZL7sprintfPcPKcz.constprop.0    #
.L7:
 # assembly.cpp:14:     x=1000;
    movl    $1000, x(%rip)   #, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_15
    cmpl    $5, %eax     #, x.3_15
    jle .L8  #,
    .p2align 4,,10
.L9:
 # assembly.cpp:16:         x--;
    movl    x(%rip), %eax    # x, x.4_3
    subl    $1, %eax     #, _4
    movl    %eax, x(%rip)    # _4, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_2
    cmpl    $5, %eax     #, x.3_2
    jg  .L9  #,
.L8:
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    addq    $104, %rsp   #,
    ret 
.L11:
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC1(%rip), %rcx     #,
    call    _ZL6printfPKcz.constprop.1   #
    jmp .L7  #
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

Di sini Anda dapat melihat bahwa kode perakitan untuk sprintf, printfdan whileloop dihasilkan. Keuntungannya adalah jika xvariabel diubah oleh beberapa program eksternal atau perangkat keras, sprintfbagian dari kode akan dieksekusi. Dan juga demikianwhile loop dapat digunakan untuk menunggu sibuk sekarang.


0

Jawaban lain sudah menyebutkan menghindari beberapa optimasi untuk:

  • gunakan register yang dipetakan memori (atau "MMIO")
  • tulis driver perangkat
  • memungkinkan debugging program yang lebih mudah
  • membuat perhitungan floating point lebih deterministik

Volatile sangat penting setiap kali Anda membutuhkan nilai yang tampaknya berasal dari luar dan tidak dapat diprediksi dan menghindari optimisasi kompiler berdasarkan nilai yang diketahui, dan ketika suatu hasil tidak benar-benar digunakan tetapi Anda membutuhkannya untuk dihitung, atau itu digunakan tetapi Anda ingin menghitungnya beberapa kali untuk tolok ukur, dan Anda perlu perhitungan untuk memulai dan mengakhiri pada titik yang tepat.

Pembacaan volatile seperti operasi input (suka scanfatau penggunaan cin): nilainya tampaknya berasal dari luar program, jadi setiap perhitungan yang memiliki ketergantungan pada nilai harus dimulai setelahnya .

Tulisan yang tidak stabil seperti operasi keluaran (suka printfatau penggunaan cout): nampaknya dikomunikasikan di luar program, jadi jika nilainya tergantung pada perhitungan, ia harus diselesaikan sebelum .

Jadi sepasang volatile read / write dapat digunakan untuk menjinakkan tolok ukur dan membuat pengukuran waktu menjadi bermakna .

Tanpa volatile, perhitungan Anda dapat dimulai oleh kompiler sebelumnya, karena tidak ada yang akan mencegah penyusunan ulang perhitungan dengan fungsi seperti pengukuran waktu .

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.