Definisi dari volatile
volatile
memberitahu kompiler bahwa nilai variabel dapat berubah tanpa diketahui kompiler. Oleh karena itu kompiler tidak dapat menganggap nilai tidak berubah hanya karena program C tampaknya tidak mengubahnya.
Di sisi lain, itu berarti bahwa nilai variabel mungkin diperlukan (baca) di tempat lain yang tidak diketahui oleh kompiler, oleh karena itu ia harus memastikan bahwa setiap tugas pada variabel benar-benar dilakukan sebagai operasi tulis.
Gunakan kasing
volatile
diperlukan saat
- mewakili register perangkat keras (atau I / O yang dipetakan memori) sebagai variabel - bahkan jika register tidak akan pernah dibaca, kompiler tidak boleh hanya melewati operasi penulisan dengan berpikir "Programmer bodoh. Mencoba untuk menyimpan nilai dalam variabel yang dia / dia tidak akan pernah membaca kembali. Dia bahkan tidak akan memperhatikan jika kita menghilangkan tulisannya. " Sebaliknya, bahkan jika program tidak pernah menulis nilai ke variabel, nilainya masih dapat diubah oleh perangkat keras.
- berbagi variabel antara konteks eksekusi (mis. ISR / program utama) (lihat jawaban @ kkramo)
Efek dari volatile
Ketika sebuah variabel dideklarasikan volatile
, kompiler harus memastikan bahwa setiap penugasan dalam kode program tercermin dalam operasi penulisan yang sebenarnya, dan bahwa setiap pembacaan dalam kode program membaca nilai dari memori (yang dipetakan).
Untuk variabel yang tidak mudah menguap, kompiler menganggap ia tahu jika / ketika nilai variabel berubah dan dapat mengoptimalkan kode dengan cara yang berbeda.
Untuk satu, kompiler dapat mengurangi jumlah baca / tulis ke memori, dengan menjaga nilai dalam register CPU.
Contoh:
void uint8_t compute(uint8_t input) {
uint8_t result = input + 2;
result = result * 2;
if ( result > 100 ) {
result -= 100;
}
return result;
}
Di sini, kompiler mungkin bahkan tidak akan mengalokasikan RAM untuk result
variabel, dan tidak akan pernah menyimpan nilai-nilai perantara di mana pun kecuali dalam register CPU.
Jika result
volatile, setiap kejadian result
dalam kode C akan memerlukan kompiler untuk melakukan akses ke RAM (atau port I / O), yang mengarah ke kinerja yang lebih rendah.
Kedua, kompiler dapat memesan ulang operasi pada variabel non-volatile untuk kinerja dan / atau ukuran kode. Contoh sederhana:
int a = 99;
int b = 1;
int c = 99;
bisa dipesan ulang
int a = 99;
int c = 99;
int b = 1;
yang dapat menyimpan instruksi assembler karena nilai 99
tidak harus dimuat dua kali.
Jika a
, b
dan c
volatile kompiler harus memancarkan instruksi yang menetapkan nilai-nilai dalam urutan yang tepat seperti yang diberikan dalam program.
Contoh klasik lainnya adalah seperti ini:
volatile uint8_t signal;
void waitForSignal() {
while ( signal == 0 ) {
// Do nothing.
}
}
Jika, dalam kasus ini, signal
tidak volatile
, kompiler akan 'berpikir' bahwa while( signal == 0 )
mungkin merupakan infinite loop (karena signal
tidak akan pernah diubah oleh kode di dalam loop ) dan mungkin menghasilkan setara dengan
void waitForSignal() {
if ( signal != 0 ) {
return;
} else {
while(true) { // <-- Endless loop!
// do nothing.
}
}
}
Pertimbangan penanganan volatile
nilai
Seperti yang dinyatakan di atas, volatile
variabel dapat memperkenalkan penalti kinerja ketika diakses lebih sering daripada yang sebenarnya diperlukan. Untuk mengurangi masalah ini, Anda bisa "membatalkan volatile" nilainya dengan menetapkan variabel yang tidak mudah menguap, seperti
volatile uint32_t sysTickCount;
void doSysTick() {
uint32_t ticks = sysTickCount; // A single read access to sysTickCount
ticks = ticks + 1;
setLEDState( ticks < 500000L );
if ( ticks >= 1000000L ) {
ticks = 0;
}
sysTickCount = ticks; // A single write access to volatile sysTickCount
}
Ini mungkin sangat bermanfaat di ISR di mana Anda ingin secepat mungkin tidak mengakses perangkat keras atau memori yang sama beberapa kali ketika Anda tahu itu tidak diperlukan karena nilainya tidak akan berubah saat ISR Anda berjalan. Ini umum ketika ISR adalah 'penghasil' nilai untuk variabel, seperti sysTickCount
pada contoh di atas. Pada AVR akan sangat menyakitkan untuk memiliki fungsi doSysTick()
mengakses empat byte yang sama dalam memori (empat instruksi = 8 siklus CPU per akses ke sysTickCount
) lima atau enam kali alih-alih hanya dua kali, karena programmer tahu bahwa nilainya tidak akan diubah dari beberapa kode lain saat doSysTick()
menjalankannya.
Dengan trik ini, Anda pada dasarnya melakukan hal yang sama persis seperti yang dilakukan oleh kompiler untuk variabel non-volatil, yaitu membacanya dari memori hanya ketika harus, menyimpan nilai dalam register untuk beberapa waktu dan menulis kembali ke memori hanya ketika harus ; tetapi kali ini, Anda tahu lebih baik daripada kompiler jika / ketika membaca / menulis harus terjadi, jadi Anda membebaskan kompiler dari tugas optimasi ini dan melakukannya sendiri.
Keterbatasan volatile
Akses non-atom
volatile
tidak tidak memberikan akses atom untuk variabel multi-kata. Untuk kasus-kasus tersebut, Anda harus memberikan pengecualian bersama dengan cara lain, selain menggunakan volatile
. Pada AVR, Anda dapat menggunakan ATOMIC_BLOCK
dari <util/atomic.h>
atau cli(); ... sei();
panggilan sederhana . Makro masing-masing bertindak sebagai penghalang memori juga, yang penting ketika datang ke urutan akses:
Perintah eksekusi
volatile
membebankan pesanan eksekusi ketat hanya sehubungan dengan variabel volatil lainnya. Ini artinya, misalnya
volatile int i;
volatile int j;
int a;
...
i = 1;
a = 99;
j = 2;
dijamin pertama menetapkan 1 untuk i
dan kemudian menetapkan 2 untuk j
. Namun, itu tidak dijamin yang a
akan ditugaskan di antara; kompiler dapat melakukan tugas itu sebelum atau setelah potongan kode, pada dasarnya setiap saat hingga pembacaan pertama (terlihat) a
.
Jika bukan karena penghalang memori makro yang disebutkan di atas, kompiler akan diizinkan untuk menerjemahkan
uint32_t x;
cli();
x = volatileVar;
sei();
untuk
x = volatileVar;
cli();
sei();
atau
cli();
sei();
x = volatileVar;
(Demi kelengkapan, saya harus mengatakan bahwa hambatan ingatan, seperti yang tersirat oleh makro sei / cli, dapat benar-benar meniadakan penggunaan volatile
, jika semua akses dihubungkan dengan hambatan-hambatan ini.)