की एक परिभाषा volatile
volatile
संकलक को बताता है कि संकलक को जानने के बिना चर का मान बदल सकता है। इसलिए कंपाइलर मान नहीं सकता है कि मूल्य सिर्फ इसलिए नहीं बदला क्योंकि C प्रोग्राम ने इसे नहीं बदला है।
दूसरी ओर, इसका मतलब है कि चर के मूल्य की आवश्यकता हो सकती है (पढ़ें) कहीं और संकलक के बारे में नहीं जानता है, इसलिए यह सुनिश्चित करना चाहिए कि चर के प्रत्येक असाइनमेंट को वास्तव में एक लेखन ऑपरेशन के रूप में किया जाता है।
बक्सों का इस्तेमाल करें
volatile
जब आवश्यक है
- हार्डवेयर रजिस्टरों (या मेमोरी-मैप्ड I / O) को वेरिएबल्स के रूप में प्रस्तुत करना - भले ही रजिस्टर कभी नहीं पढ़ा जाएगा, कंपाइलर को केवल लिखने के संचालन को छोड़ना नहीं चाहिए "बेवकूफ प्रोग्रामर। एक चर में एक मान को संग्रहीत करने की कोशिश करता है जो वह या वह है।" कभी वापस नहीं पढ़ा जाएगा। वह / वह भी ध्यान नहीं देंगे अगर हम लिखना छोड़ दें। इसके विपरीत, भले ही प्रोग्राम कभी भी वैरिएबल के लिए वैल्यू नहीं लिखता है, लेकिन इसके मूल्य को हार्डवेयर द्वारा बदला जा सकता है।
- निष्पादन संदर्भों के बीच चर साझा करना (जैसे ISR / मुख्य कार्यक्रम) (@ kkramo का उत्तर देखें)
इसका प्रभाव volatile
जब एक चर घोषित किया जाता है volatile
तो संकलक को यह सुनिश्चित करना चाहिए कि प्रोग्राम कोड में उसके लिए प्रत्येक असाइनमेंट एक वास्तविक लेखन ऑपरेशन में परिलक्षित होता है, और प्रोग्राम कोड में पढ़ा जाने वाला प्रत्येक मेमोरी (mmapped) मेमोरी से मान पढ़ता है।
गैर-वाष्पशील चर के लिए, संकलक यह मानता है कि क्या / जब चर का मान बदलता है और विभिन्न तरीकों से कोड का अनुकूलन कर सकता है।
एक के लिए, कंपाइलर सीपीयू रजिस्टरों में वैल्यू रखकर, रीड / राइट टू मेमोरी की संख्या को कम कर सकता है।
उदाहरण:
void uint8_t compute(uint8_t input) {
uint8_t result = input + 2;
result = result * 2;
if ( result > 100 ) {
result -= 100;
}
return result;
}
यहां, कंपाइलर शायद result
चर के लिए RAM आवंटित नहीं करेगा, और कभी भी मध्यवर्ती मानों को स्टोर नहीं करेगा लेकिन एक सीपीयू रजिस्टर में।
यदि result
अस्थिर था, result
तो सी कोड में होने वाली हर घटना के लिए कंपाइलर को रैम (या I / O पोर्ट) तक पहुंच की आवश्यकता होती है, जिससे प्रदर्शन कम होता है।
दूसरे, संकलक प्रदर्शन और / या कोड आकार के लिए गैर-वाष्पशील चर पर संचालन को फिर से आदेश दे सकता है। सरल उदाहरण:
int a = 99;
int b = 1;
int c = 99;
के लिए फिर से आदेश दिया जा सकता है
int a = 99;
int c = 99;
int b = 1;
जो एक कोडांतरक निर्देश को बचा सकता है क्योंकि मूल्य 99
को दो बार लोड नहीं करना पड़ेगा।
यदि a
, b
और c
अस्थिर थे , तो कंपाइलर को निर्देशों का उत्सर्जन करना होगा जो प्रोग्राम में दिए गए मानों को सटीक क्रम में असाइन करते हैं।
अन्य क्लासिक उदाहरण इस प्रकार है:
volatile uint8_t signal;
void waitForSignal() {
while ( signal == 0 ) {
// Do nothing.
}
}
यदि इस मामले में, संकलक signal
नहीं थे volatile
, तो संकलक 'सोचता है' कि while( signal == 0 )
एक अनंत लूप हो सकता है (क्योंकि लूप signal
के अंदर कोड द्वारा कभी नहीं बदला जाएगा ) और इसके बराबर उत्पन्न हो सकता है
void waitForSignal() {
if ( signal != 0 ) {
return;
} else {
while(true) { // <-- Endless loop!
// do nothing.
}
}
}
volatile
मूल्यों को संभालने पर विचार करें
जैसा कि ऊपर कहा गया है, एक volatile
चर प्रदर्शन जुर्माना लगा सकता है जब इसे वास्तव में आवश्यक से अधिक बार एक्सेस किया जाता है। इस समस्या को कम करने के लिए, आप गैर-वाष्पशील चर के लिए असाइनमेंट द्वारा मान को "अन-वाष्पशील" कर सकते हैं, जैसे
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
}
यह ISR में विशेष रूप से फायदेमंद हो सकता है जहाँ आप एक ही हार्डवेयर या मेमोरी को एक से अधिक बार एक्सेस नहीं करना चाहते हैं, जब आप जानते हैं कि इसकी आवश्यकता नहीं है क्योंकि आपके ISR के चलने के दौरान मान नहीं बदलेगा। यह सामान्य है जब ISR, चर के लिए मानों का 'निर्माता' है, जैसे sysTickCount
ऊपर दिए गए उदाहरण में। AVR पर यह विशेष रूप से दर्दनाक होगा कि फ़ंक्शन doSysTick()
को मेमोरी में समान चार बाइट्स तक पहुंचना होगा (चार निर्देश = 8 सीपीयू चक्र प्रति एक्सेस sysTickCount
) केवल दो बार के बजाय पांच या छह बार, क्योंकि प्रोग्रामर को पता है कि मूल्य नहीं होगा अपने doSysTick()
रन के दौरान किसी अन्य कोड से बदला जाए ।
इस ट्रिक के साथ, आप अनिवार्य रूप से ठीक वही काम करते हैं जो कंपाइलर गैर-वाष्पशील चरों के लिए करता है, अर्थात उन्हें मेमोरी से केवल तभी पढ़ें जब उसे करना हो, कुछ समय के लिए एक रजिस्टर में वैल्यू रखें और उसे केवल तब ही मेमोरी में लिखें। ; लेकिन इस बार, आप संकलक की तुलना में बेहतर जानते हैं / जब पढ़ता / राईट चाहिए होता है, तो आप इस अनुकूलन कार्य से संकलक को राहत देने और यह अपने आप करो।
की सीमाएँ volatile
गैर-परमाणु पहुंच
volatile
बहु-शब्द चर के लिए परमाणु पहुँच प्रदान नहीं करता है । उन मामलों के लिए, आपको उपयोग करने के अलावा , अन्य माध्यमों से पारस्परिक बहिष्कार प्रदान करना होगा volatile
। AVR पर, आप साधारण कॉल ATOMIC_BLOCK
से <util/atomic.h>
या उपयोग कर सकते हैं cli(); ... sei();
। संबंधित मैक्रो मेमोरी बैरियर के रूप में भी कार्य करता है, जो एक्सेस के क्रम में आने पर महत्वपूर्ण है:
निष्पादन आदेश
volatile
केवल अन्य अस्थिर चर के संबंध में सख्त निष्पादन आदेश लागू करता है। इसका मतलब है कि, उदाहरण के लिए
volatile int i;
volatile int j;
int a;
...
i = 1;
a = 99;
j = 2;
पहले 1 से असाइन करने की गारंटी है i
और फिर 2 से असाइन करने के लिए j
। हालांकि, यह गारंटी नहीं है कि a
बीच में सौंपा जाएगा; संकलक कोड स्निपेट के पहले या बाद में, मूल रूप से पहले पढ़े (दिखाई देने वाले) तक किसी भी समय कर सकता है a
।
यदि यह उपर्युक्त मैक्रोज़ की मेमोरी बाधा के लिए नहीं थे, तो संकलक को अनुवाद करने की अनुमति होगी
uint32_t x;
cli();
x = volatileVar;
sei();
सेवा
x = volatileVar;
cli();
sei();
या
cli();
sei();
x = volatileVar;
(पूर्णता के लिए मुझे यह कहना होगा कि मेमोरी बैरियर, जैसे कि sei / cli macros द्वारा निहित हैं, वास्तव में इसके उपयोग को कम कर सकते हैं volatile
, यदि सभी एक्सेस इन बैरियर के साथ ब्रैकेट किए गए हों।)