एम्बेडेड सी विकास में अस्थिरता का उपयोग करना


44

मैं कुछ लेखों को पढ़ रहा हूं और volatileकंपाइलर का उपयोग करके ऑब्जेक्ट पर किसी भी अनुकूलन को लागू करने से रोकने के लिए कीवर्ड के उपयोग के बारे में स्टैक एक्सचेंज के उत्तर उन तरीकों से बदल सकते हैं जो कंपाइलर द्वारा निर्धारित नहीं किए जा सकते हैं।

यदि मैं एडीसी (चर को कॉल करता adcValueहूं) से पढ़ रहा हूं , और मैं इस चर को वैश्विक घोषित कर रहा हूं, तो क्या मुझे volatileइस मामले में कीवर्ड का उपयोग करना चाहिए ?

  1. volatileकीवर्ड का उपयोग किए बिना

    // Includes
    #include "adcDriver.h"
    
    // Global variables
    uint16_t adcValue;
    
    // Some code
    void readFromADC(void)
    {
       adcValue = readADC();
    }
    
  2. volatileकीवर्ड का उपयोग करना

    // Includes
    #include "adcDriver.h"
    
    // Global variables
    volatile uint16_t adcValue;
    
    // Some code
    void readFromADC(void)
    {
       adcValue = readADC();
    }
    

मैं यह सवाल इसलिए पूछ रहा हूं क्योंकि जब डिबगिंग होती है, तो मैं दोनों दृष्टिकोणों के बीच कोई अंतर नहीं देख सकता हूं, हालांकि सर्वोत्तम प्रथाओं का कहना है कि मेरे मामले में (एक वैश्विक चर जो हार्डवेयर से सीधे बदलता है), फिर उपयोग volatileकरना अनिवार्य है।


1
कई डिबग वातावरण (निश्चित रूप से जीसीसी) कोई अनुकूलन लागू नहीं करते हैं। एक उत्पादन सामान्य रूप से होगा (आपकी पसंद के आधार पर)। इससे बिल्ड के बीच 'दिलचस्प' अंतर पैदा हो सकता है। लिंकर आउटपुट मैप को देखते हुए जानकारीपूर्ण है।
पीटर स्मिथ

22
"मेरे मामले में (ग्लोबल वैरिएबल जो सीधे हार्डवेयर से बदलता है)" - आपका ग्लोबल वैरिएबल हार्डवेयर से नहीं बल्कि केवल आपके सी कोड से बदला जाता है , जिसके बारे में कंपाइलर को पता होता है। - वह हार्डवेयर रजिस्टर जिसमें ADC परिणाम प्रदान करता है, हालाँकि, अस्थिर होना चाहिए, क्योंकि संकलक यह नहीं जान सकता है कि कब / कब उसका मूल्य बदल जाएगा (यदि ADC हार्डवेयर रूपांतरण को समाप्त करता है / करती है)।
जिम्बी

2
क्या आपने दोनों संस्करणों द्वारा उत्पन्न कोडांतरक की तुलना की है? आपको यह दिखाना चाहिए कि हुड के नीचे क्या हो रहा है
मग

3
@ स्टार्क: BIOS? एक माइक्रोकंट्रोलर पर? कैशिंग नियमों और मेमोरी मैप के बीच डिज़ाइन की संगति से मेमोरी-मैप्ड I / O स्पेस नॉन-कैचेबल होगा (यदि आर्किटेक्चर के पास पहले से डेटा कैशे भी है, जो सुनिश्चित नहीं है)। लेकिन वाष्पशील का मेमोरी कंट्रोलर कैश से कोई लेना-देना नहीं है।
बेन वोइगट

1
@Davislor भाषा मानक को सामान्य रूप से अधिक कुछ भी कहने की आवश्यकता नहीं है। वाष्पशील वस्तु के लिए पढ़ा जाने वाला एक वास्तविक भार प्रदर्शन करेगा (भले ही संकलक ने हाल ही में एक किया हो और आमतौर पर यह जानता होगा कि मूल्य क्या है) और इस तरह की वस्तु के लिए एक लेखन एक वास्तविक दुकान का प्रदर्शन करेगा (भले ही वस्तु से समान मूल्य पढ़ा गया हो )। तो if(x==1) x=1;लेखन में एक गैर वाष्पशील के लिए दूर अनुकूलित किया जा सकता है xऔर अगर xअस्थिर है तो अनुकूलित नहीं किया जा सकता है। OTOH यदि बाहरी उपकरणों तक पहुंचने के लिए विशेष निर्देशों की आवश्यकता होती है, तो यह उन पर जोड़ना है (यदि कोई मेमोरी रेंज के माध्यम से लिखना आवश्यक है)।
जिज्ञासु

जवाबों:


87

की एक परिभाषा 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, यदि सभी एक्सेस इन बैरियर के साथ ब्रैकेट किए गए हों।)


7
प्रदर्शन के लिए संयुक्त राष्ट्र के अस्थिरता की अच्छी चर्चा :)
awjlogan

3
मैं हमेशा ISO / IEC 9899: 1999 6.7.3 (6) में अस्थिरता की परिभाषा का उल्लेख करना पसंद करता हूं : An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. अधिक लोगों को इसे पढ़ना चाहिए।
Jeroen3

3
यह ध्यान देने योग्य हो सकता है कि cli/ seiबहुत भारी समाधान है यदि आपका एकमात्र लक्ष्य एक स्मृति अवरोध को प्राप्त करना है, न कि रुकावट को रोकना। ये मैक्रोज़ वास्तविक cli/ seiनिर्देश उत्पन्न करते हैं , और इसके अलावा क्लोबर मेमोरी, और यह क्लोबिंग है जिसके परिणामस्वरूप बाधा उत्पन्न होती है। व्यवधान को निष्क्रिय किए बिना केवल एक स्मृति अवरोधक होने के लिए आप अपने स्वयं के स्थूल को शरीर के साथ परिभाषित कर सकते हैं जैसे __asm__ __volatile__("":::"memory")(अर्थात मेमोरी क्लॉबर के साथ खाली असेंबली कोड)।
रुस्लान

3
@ नीचार्टली नंबर C17 5.1.2.3 ines6 अवलोकनीय व्यवहार को परिभाषित करता है : "अमूर्त मशीन के नियमों के अनुसार अस्थिर वस्तुओं तक पहुंच का कड़ाई से मूल्यांकन किया जाता है।" C मानक वास्तव में स्पष्ट नहीं है कि स्मृति अवरोधों की आवश्यकता कहाँ है। एक अभिव्यक्ति के अंत में जो उपयोग करता volatileहै वह एक अनुक्रम बिंदु है, और इसके बाद सब कुछ "बाद में अनुक्रम" होना चाहिए। कि अभिव्यक्ति मतलब है एक तरह के स्मृति बाधा। कंपाइलर विक्रेताओं ने प्रोग्रामर पर मेमोरी बाधाओं की जिम्मेदारी डालने के लिए सभी प्रकार के मिथकों को फैलाने का विकल्प चुना, लेकिन यह "अमूर्त मशीन" के नियमों का उल्लंघन करता है।
लुंडिन

2
@JimmyB स्थानीय अस्थिर जैसे कोड के लिए उपयोगी हो सकता है volatile data_t data = {0}; set_mmio(&data); while (!data.ready);
मैकीज पीचोटका

13

वाष्पशील कीवर्ड संकलक को बताता है कि चर तक पहुंच का अवलोकन प्रभाव पड़ता है। इसका मतलब यह है कि हर बार जब आपका स्रोत कोड चर का उपयोग करता है तो संकलक को चर तक पहुंच बनाना होगा। हो कि एक पढ़ने या लिखने का उपयोग।

इसका प्रभाव यह है कि सामान्य कोड प्रवाह के बाहर परिवर्तनशील परिवर्तन को भी कोड द्वारा देखा जाएगा। उदाहरण के लिए, यदि कोई बाधा हैंडलर मान बदलता है। या अगर चर वास्तव में कुछ हार्डवेयर रजिस्टर है जो अपने आप में बदल जाता है।

यह महान लाभ भी इसका नकारात्मक पहलू है। चर के लिए हर एक पहुंच चर के माध्यम से चला जाता है और किसी भी राशि के लिए तेजी से पहुंच के लिए मूल्य को कभी भी रजिस्टर में नहीं रखा जाता है। इसका मतलब है कि एक अस्थिर चर धीमा होगा। मैग्निट्यूड्स धीमा। इसलिए केवल वाष्पशील का उपयोग करें जहां यह वास्तव में आवश्यक है।

आपके मामले में, जहां तक ​​आपने कोड दिखाया है, वैश्विक चर केवल तभी बदला जाता है जब आप इसे स्वयं अपडेट करते हैं adcValue = readADC();। कंपाइलर जानता है कि ऐसा कब होता है और कभी भी रजिस्टर में adcValue के मूल्य को किसी ऐसी चीज़ में नहीं रखेगा जो readFromADC()फ़ंक्शन कह सकती है । या किसी भी फ़ंक्शन के बारे में पता नहीं है। या ऐसा कुछ जो पॉइंटर्स में हेरफेर करेगा जो इंगित कर सकता है adcValueऔर ऐसे। वास्तव में अस्थिरता की कोई आवश्यकता नहीं है क्योंकि परिवर्तनशील अप्रत्याशित तरीकों में कभी नहीं बदलता है।


6
मैं इस जवाब से सहमत हूं लेकिन "परिमाण धीमा" लगता है बहुत गंभीर है।
kkrambo

6
एक सीपीयू रजिस्टर को आधुनिक सुपरस्लेकर सीपीयू पर एक सीपीयू चक्र से कम में एक्सेस किया जा सकता है। दूसरी ओर वास्तविक अनकही मेमोरी तक पहुंच (याद रखें कि कुछ बाहरी हार्डवेयर इसे बदल देगा, इसलिए कोई सीपीयू कैश की अनुमति नहीं है) 100-300 सीपीयू चक्र की सीमा में हो सकता है। तो, हाँ, परिमाण। एक AVR या समान माइक्रो नियंत्रक पर इतना बुरा नहीं होगा लेकिन सवाल हार्डवेयर निर्दिष्ट नहीं करता है।
गोसविन वॉन ब्रेडरलो 13

7
एम्बेडेड (माइक्रोकंट्रोलर) सिस्टम में, रैम एक्सेस के लिए दंड अक्सर कम होता है। AVR, उदाहरण के लिए, रैम से पढ़ने या लिखने के लिए केवल दो सीपीयू चक्र लेते हैं (एक रजिस्टर-रजिस्टर चाल में एक चक्र लगता है), इसलिए रजिस्टर दृष्टिकोण में चीजों को रखने की बचत (लेकिन वास्तव में कभी नहीं पहुंचती) अधिकतम। प्रति उपयोग 2 घड़ी चक्र। - बेशक, अपेक्षाकृत बोलने वाला, रजिस्टर एक्स से रैम तक के मूल्य को बचाता है, फिर तुरंत उस गणना को रजिस्टर एक्स में आगे लोड करने के लिए 0 चक्रों के बजाय 2x2 = 4 ले जाएगा (जब एक्स में केवल मान रखते हैं), और इसलिए असीम है धीमी :)
जिमीबी

1
"किसी विशेष चर से लिखना या पढ़ना" के संदर्भ में यह 'परिमाण धीमा' है, हाँ। हालाँकि, एक पूर्ण कार्यक्रम के संदर्भ में जो एक से अधिक चर को बार-बार पढ़ने / लिखने से अधिक संभव है, वास्तव में नहीं। उस स्थिति में समग्र अंतर 'छोटे से नगण्य' होने की संभावना है। प्रदर्शन के बारे में जोर देते समय, यह स्पष्ट करने के लिए ध्यान रखा जाना चाहिए कि क्या अभिकथन एक विशेष ऑप से संबंधित है या एक पूरे के रूप में एक कार्यक्रम से संबंधित है। ~ 300x के कारक द्वारा एक बार उपयोग किए गए सेशन को धीमा करना लगभग कभी भी बड़ी बात नहीं है।
३th

1
आपका मतलब है, कि आखिरी वाक्य? यह "समय से पहले अनुकूलन सभी बुराई की जड़ है" के अर्थ में बहुत अधिक है। जाहिर है कि आपको volatileहर चीज का इस्तेमाल सिर्फ इसलिए नहीं करना चाहिए, बल्कि आपको उन मामलों से भी नहीं बचना चाहिए, जहां आपको लगता है कि इसे प्रीमेप्टिव परफॉर्मेंस की चिंता के कारण वैध तरीके से बुलाया जाता है।
एरोथ

9

एम्बेडेड सी अनुप्रयोगों पर अस्थिर कीवर्ड का मुख्य उपयोग एक वैश्विक चर को चिह्नित करना है जो एक बाधा हैंडलर में लिखा गया है। यह निश्चित रूप से इस मामले में वैकल्पिक नहीं है।

इसके बिना, संकलक यह साबित नहीं कर सकता है कि मूल्य कभी भी आरंभीकरण के बाद लिखा गया है, क्योंकि यह साबित नहीं कर सकता है कि बाधा हैंडलर को कभी भी कहा जाता है। इसलिए यह सोचता है कि यह चर को अस्तित्व से बाहर कर सकता है।


2
निश्चित रूप से अन्य व्यावहारिक उपयोग मौजूद हैं, लेकिन यह सबसे आम है।
vicatcu

1
यदि मूल्य केवल एक ISR में पढ़ा जाता है (और मुख्य () से बदला गया है), तो आपको संभावित रूप से अस्थिर का उपयोग करना होगा और बहु ​​बाइट चर के लिए एटीओएमआईसी की गारंटी भी देनी होगी।
Rev1.0

15
@ Rev1.0 नहीं, अस्थिरता सुगंध की गारंटी नहीं देती है। उस चिंता को अलग से संबोधित किया जाना चाहिए।
क्रिस स्ट्रैटन

1
हार्डवेयर से कोई पढ़ा नहीं है और न ही पोस्ट किए गए कोड में कोई व्यवधान है। आप उस प्रश्न से चीजों को ग्रहण कर रहे हैं जो वहां नहीं है। इसका वर्तमान रूप में उत्तर नहीं दिया जा सकता है।
लुंडिन

3
"एक वैश्विक चर को चिह्नित करें जो एक बाधा हैंडलर में लिखा गया है"। यह एक चर को चिह्नित करने के लिए है; वैश्विक या अन्यथा; यह समझ में आने वाले कंपाइलरों के बाहर कुछ बदल सकता है। बाधा की आवश्यकता नहीं है। यह स्मृति या किसी को स्मृति में एक जांच चिपके हुए साझा किया जा सकता है (बाद में 40 से अधिक आधुनिक कुछ के लिए अनुशंसित नहीं है)
UKMonkey

9

दो मामले मौजूद हैं जहाँ आपको volatileएम्बेडेड सिस्टम का उपयोग करना चाहिए ।

  • हार्डवेयर रजिस्टर से पढ़ते समय।

    इसका मतलब है, मेमोरी-मैप्ड खुद को पंजीकृत करता है, एमसीयू के अंदर हार्डवेयर बाह्य उपकरणों का हिस्सा। यह संभवतः "ADC0DR" जैसे कुछ गुप्त नाम होगा। यह रजिस्टर सी कोड में परिभाषित किया जाना चाहिए, या तो उपकरण विक्रेता द्वारा वितरित कुछ रजिस्टर मानचित्र के माध्यम से, या अपने आप से। इसे स्वयं करने के लिए, आप (16 बिट रजिस्टर मानकर) करेंगे:

    #define ADC0DR (*(volatile uint16_t*)0x1234)

    जहाँ 0x1234 वह पता है जहाँ MCU ने रजिस्टर मैप किया है। चूंकि volatileपहले से ही उपरोक्त मैक्रो का हिस्सा है, इसलिए इसके लिए कोई भी पहुंच अस्थिर-योग्य होगी। तो यह कोड ठीक है:

    uint16_t adc_data;
    adc_data = ADC0DR;
  • आईएसआर के परिणाम का उपयोग करके एक आईएसआर और संबंधित कोड के बीच एक चर साझा करते समय।

    यदि आपके पास ऐसा कुछ है:

    uint16_t adc_data = 0;
    
    void adc_stuff (void)
    {
      if(adc_data > 0)
      {
        do_stuff(adc_data);
      } 
    }
    
    interrupt void ADC0_interrupt (void)
    {
      adc_data = ADC0DR;
    }

    तब कंपाइलर सोच सकता है: "adc_data हमेशा 0 होता है क्योंकि यह कहीं भी अपडेट नहीं होता है। और ADC0_interrupt () फ़ंक्शन को कभी भी कॉल नहीं किया जाता है, इसलिए चर को बदला नहीं जा सकता है"। कंपाइलर को आमतौर पर एहसास नहीं होता है कि इंटरप्ट को सॉफ्टवेयर द्वारा नहीं बल्कि हार्डवेयर द्वारा बुलाया जाता है। इसलिए संकलक जाता है और कोड को हटा देता है if(adc_data > 0){ do_stuff(adc_data); }क्योंकि यह सोचता है कि यह कभी सच नहीं हो सकता है, जिससे एक बहुत ही अजीब और कठिन-से-डिबग बग पैदा हो सकता है।

    घोषित करके adc_data volatile, संकलक को ऐसी कोई भी धारणा बनाने की अनुमति नहीं है और इसे चर तक पहुंच को अनुकूलित करने की अनुमति नहीं है।


महत्वपूर्ण लेख:

  • ISR को हमेशा हार्डवेयर ड्राइवर के अंदर घोषित किया जाएगा। इस मामले में, एडीसी आईएसआर एडीसी चालक के अंदर होना चाहिए। कोई और नहीं बल्कि चालक को ISR के साथ संवाद करना चाहिए - बाकी सब कुछ स्पैगेटी प्रोग्रामिंग है।

  • सी लिखते समय, एक ISR और पृष्ठभूमि कार्यक्रम के बीच सभी संचार को दौड़ की स्थिति के खिलाफ संरक्षित किया जाना चाहिए। हमेशा , हर बार, कोई अपवाद नहीं। MCU डेटा बस का आकार कोई फर्क नहीं पड़ता, क्योंकि भले ही आप C में एक सिंगल 8 बिट कॉपी करते हैं, लेकिन भाषा ऑपरेशन की एटोमिसिटी की गारंटी नहीं दे सकती है। जब तक आप C11 फीचर का इस्तेमाल नहीं करते हैं _Atomic। यदि यह सुविधा उपलब्ध नहीं है, तो आपको अर्ध-विराम के कुछ तरीकों का उपयोग करना होगा या पढ़ने के दौरान अवरोध को अक्षम करना होगा। इनलाइन असेंबलर एक अन्य विकल्प है। volatileपरमाणुता की गारंटी नहीं देता है।

    यह क्या हो सकता है:
    रजिस्टर में स्टैक से
    -Lad
    मान होता है -इंटरप्ट होता है-रजिस्टर से मूल्य

    और फिर इससे कोई फर्क नहीं पड़ता कि "उपयोग मूल्य" भाग अपने आप में एक निर्देश है। अफसोस की बात है, सभी एम्बेडेड सिस्टम प्रोग्रामर का एक महत्वपूर्ण हिस्सा इस बात से बेखबर है, शायद यह अब तक का सबसे आम एम्बेडेड सिस्टम बग बना रहा है। हमेशा रुक-रुक कर, भड़काने के लिए मुश्किल, खोजने के लिए मुश्किल।


सही ढंग से लिखे गए ADC ड्राइवर का एक उदाहरण इस तरह दिखेगा (C11 _Atomicउपलब्ध नहीं है):

adc.h

// adc.h
#ifndef ADC_H
#define ADC_H

/* misc init routines here */

uint16_t adc_get_val (void);

#endif

adc.c

// adc.c
#include "adc.h"

#define ADC0DR (*(volatile uint16_t*)0x1234)

static volatile bool semaphore = false;
static volatile uint16_t adc_val = 0;

uint16_t adc_get_val (void)
{
  uint16_t result;
  semaphore = true;
    result = adc_val;
  semaphore = false;
  return result;
}

interrupt void ADC0_interrupt (void)
{
  if(!semaphore)
  {
    adc_val = ADC0DR;
  }
}
  • यह कोड मान रहा है कि एक व्यवधान अपने आप में बाधित नहीं हो सकता है। ऐसी प्रणालियों पर, एक साधारण बूलियन सेमाफोर के रूप में कार्य कर सकता है, और इसे परमाणु होने की आवश्यकता नहीं है, क्योंकि बूलियन के सेट होने से पहले कोई बाधा नहीं होती है। उपरोक्त सरलीकृत विधि का डाउन-साइड यह है कि यह ADC पढ़ता है जब रेस की स्थिति होती है, इसके बजाय पिछले मान का उपयोग करते हुए। इससे भी बचा जा सकता है, लेकिन तब कोड अधिक जटिल हो जाता है।

  • यहाँ volatileअनुकूलन कीड़े से बचाता है। इसका हार्डवेयर रजिस्टर से उत्पन्न डेटा से कोई लेना-देना नहीं है, केवल यह कि डेटा ISR के साथ साझा किया गया है।

  • staticड्राइवर को वैरिएबल बनाकर स्पेगेटी प्रोग्रामिंग और नेमस्पेस प्रदूषण से बचाता है। (यह सिंगल-कोर, सिंगल-थ्रेड एप्लिकेशन में ठीक है, लेकिन मल्टी-थ्रेडेड में नहीं है।)


डिबग करने के लिए हार्ड सापेक्ष है, यदि कोड हटा दिया जाता है, तो आप देखेंगे कि आपका मूल्यवान कोड चला गया है - यह एक बहुत ही साहसिक कथन है कि कुछ एमिस है। लेकिन मैं मानता हूं, डिबग प्रभाव के लिए बहुत अजीब और कठिन हो सकता है।
शस्त्रागार

@Arsenal यदि आपके पास एक अच्छा डीबगर है जो C के साथ असेंबलर को इनलाइन करता है, और आप कम से कम थोड़ा सा asm जानते हैं, तो हाँ स्पॉट करना आसान हो सकता है। लेकिन बड़े जटिल कोड के लिए, मशीन से निर्मित एएसएम का एक बड़ा हिस्सा गुजरने के लिए मामूली नहीं है। या यदि आप asm नहीं जानते हैं। या अगर आपका डिबगर बकवास है और asm (cougheclipsecough) नहीं दिखाता है।
लुंडिन

हो सकता है कि मैं तब लुटेरबैक डिबगर्स का उपयोग करके थोड़ा खराब हो जाऊं। यदि आप कोड में एक ब्रेकपॉइंट सेट करने का प्रयास करते हैं जो कि अनुकूलित हो गया है तो वह इसे कहीं अलग सेट कर देगा और आपको पता चल जाएगा कि कुछ चल रहा है।
आर्सेनल

@ अर्सेनल येप, जिस तरह का मिश्रित सी / एसएम आप लुटेरबैक में प्राप्त कर सकते हैं वह किसी भी तरह से मानक नहीं है। अधिकांश डिबगर्स एएसएम को एक अलग विंडो में प्रदर्शित करते हैं, यदि सभी।
लुंडिन

semaphoreनिश्चित रूप से होना चाहिए volatile! वास्तव में, यह है सबसे बुनियादी उपयोग के मामले जो के लिए कहता है एक से दूसरे निष्पादन संदर्भ से सिग्नल कुछ:। - आपके उदाहरण में, कंपाइलर केवल छोड़ सकता है क्योंकि यह 'देखता है' कि इसके मूल्य को कभी भी इससे पहले कि इसे ओवरराइट न किया जाए । volatilesemaphore = true;semaphore = false;
जिमीबी

5

प्रश्न में प्रस्तुत कोड स्निपेट्स में, वाष्पशील का उपयोग करने का कारण अभी तक नहीं है। यह अप्रासंगिक है कि adcValueएक एडीसी से मूल्य आता है। और adcValueवैश्विक होने के नाते आपको इस बात पर संदेह adcValueहोना चाहिए कि क्या अस्थिर होना चाहिए लेकिन यह खुद से एक कारण नहीं है।

वैश्विक होना एक सुराग है क्योंकि यह संभावना को खोलता है जिसे adcValueएक से अधिक प्रोग्राम संदर्भ से एक्सेस किया जा सकता है। एक कार्यक्रम के संदर्भ में एक बाधा हैंडलर और एक आरटीओएस कार्य शामिल हैं। यदि वैश्विक चर को एक संदर्भ से बदल दिया जाता है, तो दूसरे कार्यक्रम संदर्भों को यह नहीं लगता है कि वे पिछली पहुंच से मूल्य जानते हैं। प्रत्येक संदर्भ को चर मूल्य को हर बार फिर से पढ़ना होगा क्योंकि वे इसका उपयोग करते हैं क्योंकि मूल्य एक अलग कार्यक्रम के संदर्भ में बदल सकता है। जब कोई व्यवधान या कार्य स्विच घटित होता है, तो प्रोग्राम के संदर्भ में जानकारी नहीं होती है, इसलिए यह मान लेना चाहिए कि एकाधिक संदर्भों द्वारा उपयोग किए जाने वाले कोई भी वैश्विक चर संभव संदर्भ स्विच के कारण चर के किसी भी एक्सेस के बीच बदल सकते हैं। यह वही है जो अस्थिर घोषणा के लिए है। यह संकलक को बताता है कि यह चर आपके संदर्भ के बाहर बदल सकता है इसलिए इसे हर एक्सेस पढ़ें और मान लें कि आपको पहले से ही मूल्य पता नहीं है।

यदि चर एक हार्डवेयर पते पर मेमोरी-मैप किया गया है, तो हार्डवेयर द्वारा किए गए परिवर्तन आपके प्रोग्राम के संदर्भ के बाहर प्रभावी रूप से एक और संदर्भ है। तो मेमोरी-मैप भी एक सुराग है। उदाहरण के लिए, यदि आपका readADC()फ़ंक्शन ADC मान प्राप्त करने के लिए मेमोरी-मैप किए गए मान को एक्सेस करता है तो मेमोरी-मैपेड वैरिएबल शायद अस्थिर होना चाहिए।

इसलिए अपने प्रश्न पर वापस जाएं, यदि आपके कोड में अधिक है और adcValueअन्य कोड द्वारा एक्सेस किया जाता है जो एक अलग संदर्भ में चलता है, तो हां, adcValueअस्थिर होना चाहिए।


4

"ग्लोबल वैरिएबल जो हार्डवेयर से सीधे बदलता है"

सिर्फ इसलिए कि मूल्य कुछ हार्डवेयर एडीसी रजिस्टर से आ रहा है, इसका मतलब यह नहीं है कि यह हार्डवेयर द्वारा "सीधे" बदल दिया गया है।

आपके उदाहरण में, आप बस रीडैडसी () कहते हैं, जो कुछ एडीसी रजिस्टर मूल्य देता है। यह संकलक के संबंध में ठीक है, यह जानते हुए कि उस बिंदु पर adcValue को एक नया मान सौंपा गया है।

यह अलग होगा यदि आप नए मान को असाइन करने के लिए एडीसी इंटरप्ट रुटीन का उपयोग कर रहे थे, इसे तब कहा जाता है जब एक नया एडीसी मान तैयार होता है। उस स्थिति में, कंपाइलर के पास इस बात का कोई सुराग नहीं होगा कि संबंधित ISR को कब बुलाया जाएगा और यह निर्णय ले सकता है कि इस तरह से adcValue एक्सेस नहीं किया जाएगा। यह वह जगह है जहाँ अस्थिर मदद करेगा।


1
जैसा कि आपका कोड ISR फ़ंक्शन को "कॉल" कभी नहीं करता है, कंपाइलर देखता है कि चर केवल एक फ़ंक्शन में अपडेट किया गया है जिसे कोई भी कॉल नहीं करता है। इसलिए कंपाइलर इसका अनुकूलन करता है।
स्वानंद

1
यह कोड के बाकी हिस्सों पर निर्भर करता है, अगर एडक्वायु कहीं भी नहीं पढ़ा जा रहा है (जैसे केवल डिबगर के माध्यम से पढ़ा जाता है), या यदि यह केवल एक ही बार एक ही स्थान पर पढ़ा जाता है, तो संकलक संभवतः इसका अनुकूलन करेगा।
डेमियन

2
@ डैमियन: यह हमेशा "निर्भर करता है", लेकिन मैं वास्तविक प्रश्न को संबोधित करना चाहता था "क्या मुझे इस मामले में कीवर्ड का उपयोग करना चाहिए?" जितना संभव हो उतना कम।
Rev1.0

4

volatileतर्क का व्यवहार काफी हद तक आपके कोड, कंपाइलर और किए गए अनुकूलन पर निर्भर करता है।

दो उपयोग मामले हैं जहां मैं व्यक्तिगत रूप से उपयोग करता हूं volatile:

  • यदि कोई चर है जिसे मैं डिबगर के साथ देखना चाहता हूं, लेकिन संकलक ने इसे अनुकूलित किया है (इसका मतलब है कि इसे हटा दिया है क्योंकि यह पता चला है कि यह चर होना आवश्यक नहीं है), जोड़ने volatileसे संकलक इसे रखने के लिए मजबूर कर देगा और इसलिए डिबग पर देखा जा सकता है।

  • यदि चर "कोड से बाहर" बदल सकता है, तो आमतौर पर यदि आपके पास कुछ हार्डवेयर तक पहुंच है, या यदि आप चर को किसी पते पर सीधे मैप करते हैं।

एम्बेडेड में भी कंपाइलरों में कभी-कभी कुछ कीड़े होते हैं, जो वास्तव में काम नहीं करता है, अनुकूलन करना और कभी-कभी volatileसमस्याओं को हल कर सकता है।

यह देखते हुए कि आपके चर को विश्व स्तर पर घोषित किया गया है, यह संभवतः अनुकूलित नहीं किया जाएगा, जब तक कि चर का उपयोग कोड पर किया जा रहा है, कम से कम लिखा और पढ़ा जाए।

उदाहरण:

void test()
{
    int a = 1;
    printf("%i", a);
}

इस मामले में, चर को प्रिंटफ ("% i", 1) के लिए अनुकूलित किया जाएगा;

void test()
{
    volatile int a = 1;
    printf("%i", a);
}

अनुकूलित नहीं किया जाएगा

और एक:

void delay1Ms()
{
    unsigned int i;
    for (i=0; i<10; i++)
    {
        delay10us( 10);
    }
}

इस स्थिति में, कंपाइलर (यदि आप गति के लिए ऑप्टिमाइज़ करते हैं) और इस प्रकार वेरिएबल को छोड़ सकते हैं

void delay1Ms()
{
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
       delay10us( 10);
}

आपके उपयोग के मामले के लिए, यह आपके कोड के बाकी हिस्सों पर "निर्भर हो सकता है", adcValueअन्यत्र कैसे उपयोग किया जा रहा है और संकलक संस्करण / अनुकूलन सेटिंग्स का उपयोग करते हैं।

कभी-कभी यह एक कोड के लिए कष्टप्रद हो सकता है जो बिना अनुकूलन के साथ काम करता है, लेकिन एक बार अनुकूलित होने पर टूट जाता है।

uint16_t adcValue;
void readFromADC(void)
{
  adcValue = readADC();
  printf("%i", adcValue);
}

इसे प्रिंटफ ("% i", readADC ()) के लिए अनुकूलित किया जा सकता है;

uint16_t adcValue;
void readFromADC(void)
{
  adcValue = readADC();
  printf("%i", adcValue);
  callAnotherFunction(adcValue);
}

-

uint16_t adcValue;
void readFromADC(void)
{
  adcValue = readADC();
  printf("%i", adcValue);
}

void anotherFunction()
{
   // Do something with adcValue
}

ये शायद अनुकूलित नहीं होंगे, लेकिन आप कभी नहीं जानते कि "कंपाइलर कितना अच्छा है" और कंपाइलर मापदंडों के साथ बदल सकता है। आमतौर पर अच्छे अनुकूलन वाले कंपाइलरों को लाइसेंस दिया जाता है।


1
उदाहरण के लिए a = 1; ख = एक; और सी = बी; संकलक सोच सकता है कि एक मिनट रुको, ए और बी बेकार हैं, चलो सीधे 1 से सी डालते हैं। बेशक आप अपने कोड में ऐसा नहीं करेंगे, लेकिन कंपाइलर इनको ढूंढने में आपसे बेहतर है, अगर आप अभी से ऑप्टिमाइज़्ड कोड लिखने की कोशिश करते हैं तो यह बिना पढ़े भी होगा।
डेमियन

2
सही संकलक वाला एक सही कोड चालू किए गए अनुकूलन के साथ नहीं टूटेगा। कंपाइलर की शुद्धता थोड़ी समस्या है, लेकिन कम से कम आईएआर के साथ मुझे ऐसी स्थिति का सामना नहीं करना पड़ा है जहां अनुकूलन को तोड़ने वाले कोड तक ले जाना चाहिए जहां यह नहीं होना चाहिए।
आर्सेनल

5
बहुत सारे मामले जहां ऑप्टिमाइज़ेशन कोड को तोड़ता है, जब आप यूबी क्षेत्र में भी प्रवेश कर रहे होते हैं ..
पाइप

2
हां, वाष्पशील का एक साइड-इफेक्ट है कि यह डीबगिंग में सहायता कर सकता है। लेकिन यह वाष्पशील उपयोग करने का एक अच्छा कारण नहीं है। यदि आसान डिबगिंग आपका लक्ष्य है, तो आपको संभवतः अनुकूलन बंद करना चाहिए। इस उत्तर में भी व्यवधान का उल्लेख नहीं है।
kkrambo

2
डिबगिंग तर्क में जोड़कर, volatileकंपाइलर को रैम में एक वैरिएबल स्टोर करने के लिए मजबूर करता है, और वैरिएबल को असाइन करते ही उस रैम को अपडेट करने के लिए। अधिकांश समय, कंपाइलर वेरिएबल्स को 'डिलीट' नहीं करता है, क्योंकि हम आमतौर पर बिना प्रभाव के असाइनमेंट नहीं लिखते हैं, लेकिन यह वेरिएबल को कुछ सीपीयू रजिस्टर में रखने का निर्णय ले सकता है और बाद में रैम के लिए उस रजिस्टर का मान कभी नहीं लिख सकता है। डीबगर्स अक्सर सीपीयू रजिस्टर का पता लगाने में विफल होते हैं जिसमें चर आयोजित किया जाता है और इसलिए इसका मूल्य नहीं दिखाया जा सकता है।
जिमीबी

1

तकनीकी स्पष्टीकरण के बहुत सारे लेकिन मैं व्यावहारिक अनुप्रयोग पर ध्यान केंद्रित करना चाहता हूं।

volatileकीवर्ड बलों संकलक पढ़ सकते हैं या हर बार यह प्रयोग किया जाता है स्मृति से वेरिएबल का मान लिखने के लिए। आम तौर पर कंपाइलर हर बार मेमोरी एक्सेस करने के बजाय सीपीयू रजिस्टर में वैल्यू को ध्यान में रखकर अनावश्यक रीडिंग और राइटिंग नहीं करने की कोशिश करता है।

एम्बेडेड कोड में इसके दो मुख्य उपयोग हैं। सबसे पहले इसका उपयोग हार्डवेयर रजिस्टरों के लिए किया जाता है। हार्डवेयर रजिस्टर बदल सकते हैं, उदाहरण के लिए एडीसी परिधीय द्वारा एक एडीसी परिणाम रजिस्टर लिखा जा सकता है। एक्सेस करने पर हार्डवेयर रजिस्टर भी एक्शन कर सकते हैं। एक आम उदाहरण एक UART का डेटा रजिस्टर है, जो अक्सर पढ़ने के दौरान इंटरप्ट झंडे को साफ करता है।

कंपाइलर सामान्य रूप से बार-बार पढ tryे से दूर रहने की कोशिश करता है और इस धारणा पर रजिस्टर के लिखता है कि मूल्य कभी नहीं बदलेगा इसलिए इसे एक्सेस करने की कोई आवश्यकता नहीं है, लेकिन volatileकीवर्ड इसे हर बार रीड ऑपरेशन करने के लिए मजबूर करेगा।

दूसरा आम उपयोग व्यवधान और गैर-व्यवधान कोड दोनों द्वारा उपयोग किए जाने वाले चर के लिए है। व्यवधानों को सीधे नहीं कहा जाता है, इसलिए संकलक यह निर्धारित नहीं कर सकता है कि वे कब निष्पादित करेंगे, और इस तरह मान लेते हैं कि व्यवधान के अंदर कोई भी पहुंच कभी नहीं होती है। क्योंकि volatileकीवर्ड कंपाइलर को हर बार चर का उपयोग करने के लिए मजबूर करता है, इसलिए यह धारणा हटा दी जाती है।

यह ध्यान रखना महत्वपूर्ण है कि volatileकीवर्ड इन मुद्दों का पूर्ण समाधान नहीं है, और इनसे बचने के लिए सावधानी बरतनी चाहिए। उदाहरण के लिए, 8 बिट सिस्टम पर 16 बिट वैरिएबल को पढ़ने या लिखने के लिए दो मेमोरी एक्सेस की आवश्यकता होती है, और इस तरह कंपाइलर को उन एक्सेस को बनाने के लिए मजबूर किया जाता है जो वे क्रमिक रूप से होते हैं, और हार्डवेयर के लिए पहली एक्सेस पर कार्य करना संभव है या नहीं दोनों के बीच होने वाली बाधा।


0

एक volatileक्वालिफायर की अनुपस्थिति में , कोड के कुछ हिस्सों के दौरान एक वस्तु का मूल्य एक से अधिक स्थानों पर संग्रहीत किया जा सकता है। उदाहरण के लिए, कुछ इस तरह दिया गया:

int foo;
int someArray[64];
void test(void)
{
  int i;
  foo = 0;
  for (i=0; i<64; i++)
    if (someArray[i] > 0)
      foo++;
}

सी के शुरुआती दिनों में, एक संकलक ने कथन को संसाधित किया होगा

foo++;

चरणों के माध्यम से:

load foo into a register
increment that register
store that register back to foo

हालांकि, अधिक परिष्कृत संकलक, यह पहचान लेंगे कि यदि लूप के दौरान "फू" का मूल्य रजिस्टर में रखा जाता है, तो इसे केवल लूप से पहले एक बार लोड करने की आवश्यकता होगी, और एक बार बाद में संग्रहीत किया जाएगा। लूप के दौरान, हालांकि, इसका मतलब होगा कि "फू" का मूल्य दो स्थानों पर रखा जा रहा है - वैश्विक भंडारण के भीतर, और रजिस्टर के भीतर। यह एक समस्या नहीं होगी यदि कंपाइलर उन सभी तरीकों को देख सकता है जो "फू" को लूप के भीतर एक्सेस कर सकते हैं, लेकिन अगर किसी सिस्टम में कंपाइलर के बारे में पता नहीं है, तो "फू" का मान एक्सेस किया जा सकता है, तो परेशानी हो सकती है ( जैसे एक बाधा हैंडलर)।

मानक के लेखकों के लिए यह संभव हो सकता है कि वे एक नया क्वालीफायर जोड़ सकते हैं, जो स्पष्ट रूप से कंपाइलर को इस तरह के अनुकूलन करने के लिए आमंत्रित करेगा, और यह कहेगा कि पुराने जमाने के शब्दार्थ इसकी अनुपस्थिति में लागू होंगे, लेकिन ऐसे मामले जहां अनुकूलन बेहतर तरीके से लागू होते हैं। जहां यह समस्याग्रस्त होगा, इसलिए मानक इसके बजाय कंपाइलरों को यह मानने की अनुमति देता है कि ऐसी अनुकूलन सबूतों के अभाव में सुरक्षित हैं जो वे नहीं हैं। volatileकीवर्ड का उद्देश्य ऐसे सबूतों की आपूर्ति करना है।

कुछ संकलक लेखकों और प्रोग्रामर के बीच विवाद का एक बिंदु इस तरह की स्थितियों के साथ होता है:

unsigned short volatile *volatile output_ptr;
unsigned volatile output_count;

void interrupt_handler(void)
{
  if (output_count)
  {
    *((unsigned short*)0xC0001230) = *output_ptr; // Hardware I/O register
    *((unsigned short*)0xC0001234) = 1; // Hardware I/O register
    *((unsigned short*)0xC0001234) = 0; // Hardware I/O register
    output_ptr++;
    output_count--;
  }
}

void output_data_via_interrupt(unsigned short *dat, unsigned count)
{
  output_ptr = dat;
  output_count = count;
  while(output_count)
     ; // Wait for interrupt to output the data
}

unsigned short output_buffer[10];

void test(void)
{
  output_buffer[0] = 0x1234;
  output_data_via_interrupt(output_buffer, 1);
  output_buffer[0] = 0x2345;
  output_buffer[1] = 0x6789;
  output_data_via_interrupt(output_buffer,2);
}

ऐतिहासिक रूप से, अधिकांश संकलक या तो इस संभावना की अनुमति देंगे कि volatileस्टोरेज लोकेशन लिखने से मनमाना दुष्प्रभाव हो सकता है, और ऐसे स्टोर में रजिस्टरों के किसी भी मूल्य को रोकने से बचें, अन्यथा वे कॉल के फंक्शंस में रजिस्टरों में कैशिंग मूल्यों से बचेंगे योग्य नहीं है "इनलाइन", और इस प्रकार output_buffer[0]डेटा को आउटपुट करने के लिए चीजों को सेट करने के लिए 0x1234 लिखेंगे, इसके पूरा होने की प्रतीक्षा करेंगे, फिर 0x2345 पर लिखेंगे output_buffer[0], और वहां से जारी रखेंगे। स्टैंडर्ड नहीं है की आवश्यकता होती है कार्यान्वयन के पते के भंडारण का कार्य के इलाज के लिए output_bufferएक मेंvolatileएक संकेत के रूप में अयोग्य सूचक कि ऐसा कुछ हो सकता है जिसके माध्यम से संकलक समझ में नहीं आता है, हालाँकि, क्योंकि लेखकों ने सोचा कि संकलक के लेखक विभिन्न प्लेटफार्मों और उद्देश्यों के लिए अभिप्रेत हैं जब ऐसा करने से उन प्लेटफार्मों पर उन उद्देश्यों की पूर्ति होगी। बिना बताए। नतीजतन, gcc और clang जैसे कुछ "चतुर" कंपाइलर मान लेंगे कि भले ही इसका पता output_bufferदो स्टोर के बीच वाष्पशील-योग्य सूचक को लिखा हो output_buffer[0], लेकिन यह मानने का कोई कारण नहीं है कि उस वस्तु में रखे गए मूल्य के बारे में कुछ भी ध्यान रखा जा सकता है। उस समय।

इसके अलावा, जबकि पॉइंटर्स जो सीधे पूर्णांक से डाले जाते हैं, शायद ही कभी किसी उद्देश्य के लिए उपयोग किए जाते हैं, इसके अलावा चीजों को उन तरीकों से हेरफेर करने के लिए जो कंपाइलरों को समझने की संभावना नहीं है, मानक को फिर से ऐसे एक्सेसर्स का इलाज करने के लिए कंपाइलरों की आवश्यकता नहीं होती है volatile। नतीजतन, पहला लेखन *((unsigned short*)0xC0001234)"चालाक" संकलक द्वारा gcc और क्लैंग की तरह छोड़ा जा सकता है, क्योंकि ऐसे संकलक के अनुरक्षक इस बात का दावा करते हैं कि जो कोड ऐसी चीजों को अर्हता प्राप्त करने के लिए उपेक्षित है, volatileवह पहचानने की अपेक्षा "टूट" गया है कि इस तरह का कोड उपयोगी है । बहुत से विक्रेता-आपूर्ति की गई हेडर फाइलें volatileक्वालिफ़ायर को छोड़ देती हैं , और एक कंपाइलर जो कि वेंडर-सप्लाई की गई हेडर फ़ाइलों के साथ संगत होता है, वह एक से अधिक उपयोगी नहीं है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.