volatile
C में क्यों आवश्यक है? इसका क्या उपयोग है? यह क्या करेगा?
volatile
C में क्यों आवश्यक है? इसका क्या उपयोग है? यह क्या करेगा?
जवाबों:
वाष्पशील, संकलक से कहता है कि वो कुछ भी ऑप्टिमाइज़ न करें जिसे वाष्पशील चर से करना है।
इसका उपयोग करने के लिए कम से कम तीन सामान्य कारण हैं, सभी शामिल स्थितियां जहां चर का मूल्य दृश्यमान कोड से कार्रवाई के बिना बदल सकता है: जब आप हार्डवेयर के साथ इंटरफ़ेस करते हैं जो स्वयं मूल्य बदलता है; जब वहाँ एक और धागा चल रहा है जो चर का भी उपयोग करता है; या जब कोई सिग्नल हैंडलर होता है जो चर का मान बदल सकता है।
मान लें कि आपके पास हार्डवेयर का एक छोटा टुकड़ा है जो कहीं रैम में मैप किया गया है और जिसके दो पते हैं: एक कमांड पोर्ट और एक डेटा पोर्ट:
typedef struct
{
int command;
int data;
int isbusy;
} MyHardwareGadget;
अब आप कुछ कमांड भेजना चाहते हैं:
void SendCommand (MyHardwareGadget * gadget, int command, int data)
{
// wait while the gadget is busy:
while (gadget->isbusy)
{
// do nothing here.
}
// set data first:
gadget->data = data;
// writing the command starts the action:
gadget->command = command;
}
आसान लगता है, लेकिन यह विफल हो सकता है क्योंकि कंपाइलर उस क्रम को बदलने के लिए स्वतंत्र है जिसमें डेटा और कमांड लिखे गए हैं। यह हमारे छोटे गैजेट को पिछले डेटा-मूल्य के साथ कमांड जारी करने का कारण होगा। साथ ही व्यस्त लूप में प्रतीक्षा पर एक नज़र डालें। उस एक को अनुकूलित किया जाएगा। कंपाइलर चालाक बनने की कोशिश करेगा, बस एक बार isbusy के मूल्य को पढ़ें और फिर एक अनंत लूप में जाएं। वही नहीं जो आप चाहते हैं।
इसके आसपास पहुंचने का तरीका सूचक गैजेट को अस्थिर घोषित करना है। इस तरह से कंपाइलर को आपके लिखे हुए काम करने के लिए मजबूर किया जाता है। यह मेमोरी असाइनमेंट को हटा नहीं सकता है, यह रजिस्टरों में चर को कैश नहीं कर सकता है और यह असाइनमेंट के क्रम को भी नहीं बदल सकता है:
यह सही संस्करण है:
void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
{
// wait while the gadget is busy:
while (gadget->isbusy)
{
// do nothing here.
}
// set data first:
gadget->data = data;
// writing the command starts the action:
gadget->command = command;
}
volatile
सी में वास्तव में चर के मूल्यों को स्वचालित रूप से कैशिंग नहीं करने के उद्देश्य से अस्तित्व में आया। यह संकलक को इस चर के मूल्य को कैश नहीं करने के लिए बताएगा। इसलिए यह volatile
हर बार इसे सामना करने के लिए मुख्य मेमोरी से दिए गए वेरिएबल का मूल्य लेने के लिए कोड उत्पन्न करेगा । इस तंत्र का उपयोग किया जाता है क्योंकि किसी भी समय मूल्य को ओएस या किसी भी रुकावट द्वारा संशोधित किया जा सकता है। इसलिए उपयोग volatile
करने से हमें हर बार नए सिरे से मूल्य तक पहुँचने में मदद मिलेगी।
volatile
यह था कि कंपाइलरों के लिए कोड ऑप्टिमाइज़ करना संभव हो सके, जबकि प्रोग्रामर अभी भी ऐसे अनुकूलन के बिना प्राप्त किए जाने वाले शब्दार्थ को प्राप्त कर सकेंगे। मानक के लेखकों को उम्मीद थी कि गुणवत्ता कार्यान्वयन, जो भी शब्दार्थ उपयोगी थे, उनके लक्ष्य प्लेटफार्मों और अनुप्रयोग क्षेत्रों को देखते हुए समर्थन करेंगे, और उम्मीद नहीं करते थे कि संकलक लेखक मानक के अनुरूप सबसे कम गुणवत्ता वाले शब्दार्थ की पेशकश करना चाहते हैं और 100% नहीं थे। बेवकूफ (ध्यान दें कि मानक के लेखक स्पष्ट रूप से तर्क में पहचानते हैं ...
volatile
सिग्नल हैंडलर का एक और उपयोग है। यदि आपके पास इस तरह का कोड है:
int quit = 0;
while (!quit)
{
/* very small loop which is completely visible to the compiler */
}
संकलक को लूप बॉडी को quit
चर को छूने और लूप को लूप में बदलने की सूचना देने की अनुमति है while (true)
। भले ही quit
चर सिग्नल हैंडलर पर SIGINT
और के लिए सेट किया गया हो SIGTERM
; संकलक के पास यह जानने का कोई तरीका नहीं है।
हालांकि, यदि quit
चर घोषित किया जाता है volatile
, तो कंपाइलर को हर बार इसे लोड करने के लिए मजबूर किया जाता है, क्योंकि इसे कहीं और संशोधित किया जा सकता है। इस स्थिति में ठीक यही आप चाहते हैं।
quit
, संकलक मानकर इसे निरंतर लूप में अनुकूलित कर सकता है। quit
पुनरावृत्तियों के बीच परिवर्तित होने का कोई तरीका नहीं है । NB: यह वास्तव में वास्तविक थ्रेडसेफ़ प्रोग्रामिंग के लिए एक अच्छा विकल्प नहीं है।
volatile
या अन्य मार्करों की अनुपस्थिति में , यह मान लेगा कि लूप के बाहर कुछ भी नहीं है, यह वैरिएबल को एक बार संशोधित करने के बाद लूप में प्रवेश करता है, भले ही यह एक ग्लोबल वैरिएबल हो।
extern int global; void fn(void) { while (global != 0) { } }
करें gcc -O3 -S
और परिणामस्वरूप असेंबली फ़ाइल को देखें, मेरी मशीन पर यह करता है movl global(%rip), %eax
; testl %eax, %eax
; je .L1
; .L4: jmp .L4
, अगर वैश्विक शून्य नहीं है, तो यह एक अनंत लूप है। फिर जोड़ने का प्रयास करें volatile
और अंतर देखें।
volatile
संकलक को बताता है कि आपके चर को अन्य साधनों द्वारा बदला जा सकता है, जो उस कोड तक पहुंच रहा है। उदाहरण के लिए, यह एक I / O- मैप की गई मेमोरी लोकेशन हो सकती है। यदि यह ऐसे मामलों में निर्दिष्ट नहीं है, तो कुछ चर एक्सेस को अनुकूलित किया जा सकता है, उदाहरण के लिए, इसकी सामग्री को एक रजिस्टर में रखा जा सकता है, और मेमोरी स्थान को फिर से वापस नहीं पढ़ा जाता है।
आंद्रेई अलेक्जेंड्रेस्कु का यह लेख देखें, " अस्थिर - मल्टीथ्रेडेड प्रोग्रामर बेस्ट फ्रेंड "
अस्थिर कीवर्ड संकलक अनुकूलन नहीं है जो कोड कुछ अतुल्यकालिक घटनाओं की उपस्थिति में गलत प्रस्तुत करना हो सकता है को रोकने के लिए तैयार किया गया था। उदाहरण के लिए, यदि आप एक आदिम चर को अस्थिर घोषित करते हैं , तो संकलक को इसे रजिस्टर में कैश करने की अनुमति नहीं है - एक सामान्य अनुकूलन जो उस चर को कई थ्रेड्स के बीच साझा किए जाने पर विनाशकारी होगा। इसलिए सामान्य नियम यह है, यदि आपके पास आदिम प्रकार के चर हैं, जिन्हें कई धागों के बीच साझा किया जाना चाहिए, तो उन चर को अस्थिर घोषित करें। लेकिन आप वास्तव में इस कीवर्ड के साथ बहुत कुछ कर सकते हैं: आप इसका उपयोग कोड को पकड़ने के लिए कर सकते हैं जो थ्रेड सुरक्षित नहीं है, और आप संकलन समय पर ऐसा कर सकते हैं। यह लेख दिखाता है कि यह कैसे किया जाता है; समाधान में एक सरल स्मार्ट पॉइंटर शामिल है जो कोड के महत्वपूर्ण अनुभागों को क्रमबद्ध करना भी आसान बनाता है।
लेख दोनों पर लागू होता है C
और C++
।
स्कॉट मेयर्स और आंद्रेई अलेक्जेंड्रोको द्वारा " सी ++ और डबल-चेक्ड लॉकिंग के खतरों " लेख भी देखें।
इसलिए कुछ मेमोरी लोकेशन (जैसे मेमोरी मैप्ड पोर्ट या ISRs द्वारा संदर्भित मेमोरी) [इंटरप्ट सर्विस रूटीन] से निपटने के दौरान, कुछ ऑप्टिमाइज़ेशन को निलंबित कर देना चाहिए। ऐसे स्थानों के लिए विशेष उपचार निर्दिष्ट करने के लिए अस्थिरता मौजूद है, विशेष रूप से: (1) एक अस्थिर चर की सामग्री "अस्थिर" है (संकलक के लिए अज्ञात से बदल सकती है), (2) सभी अस्थिर डेटा के लिए लिखते हैं "अवलोकनीय हैं" इसलिए वे धार्मिक रूप से निष्पादित किया जाना चाहिए, और (3) अस्थिर डेटा पर सभी ऑपरेशन उसी क्रम में निष्पादित किए जाते हैं जिसमें वे स्रोत कोड में दिखाई देते हैं। पहले दो नियम उचित पढ़ना और लिखना सुनिश्चित करते हैं। अंतिम एक I / O प्रोटोकॉल के कार्यान्वयन की अनुमति देता है जो इनपुट और आउटपुट को मिलाते हैं। यह अनौपचारिक रूप से C और C ++ की अस्थिर गारंटी है।
volatile
परमाणुता की गारंटी नहीं देता है।
मेरी सरल व्याख्या है:
कुछ परिदृश्यों में, तर्क या कोड के आधार पर, कंपाइलर उन चरों का अनुकूलन करेगा जो यह सोचते हैं कि परिवर्तन नहीं करते हैं। volatile
कीवर्ड रोकता है एक चर अनुकूलित किया जा रहा।
उदाहरण के लिए:
bool usb_interface_flag = 0;
while(usb_interface_flag == 0)
{
// execute logic for the scenario where the USB isn't connected
}
उपरोक्त कोड से, संकलक सोच सकता है कि usb_interface_flag
0 के रूप में परिभाषित किया गया है, और जबकि लूप में यह हमेशा के लिए शून्य होगा। अनुकूलन के बाद, कंपाइलर इसे while(true)
हर समय मानेंगे, जिसके परिणामस्वरूप एक अनंत लूप होगा।
इस प्रकार के परिदृश्यों से बचने के लिए, हम ध्वज को अस्थिर घोषित करते हैं, हम कंपाइलर को बता रहे हैं कि यह मान बाहरी इंटरफ़ेस या प्रोग्राम के अन्य मॉड्यूल द्वारा बदला जा सकता है, अर्थात कृपया इसे ऑप्टिमाइज़ न करें। यह अस्थिरता के लिए उपयोग का मामला है।
अस्थिर के लिए एक सीमांत उपयोग निम्नलिखित है। मान लें कि आप किसी फ़ंक्शन के संख्यात्मक व्युत्पन्न की गणना करना चाहते हैं f
:
double der_f(double x)
{
static const double h = 1e-3;
return (f(x + h) - f(x)) / h;
}
समस्या यह है कि x+h-x
आमतौर h
पर राउंडऑफ़ त्रुटियों के कारण समान नहीं है । इसके बारे में सोचें: जब आप बहुत करीबी संख्याओं को प्रतिस्थापित करते हैं, तो आप बहुत सारे महत्वपूर्ण अंक खो देते हैं जो व्युत्पन्न की गणना को बर्बाद कर सकते हैं (विचार 1.00001 - 1)। एक संभावित समाधान हो सकता है
double der_f2(double x)
{
static const double h = 1e-3;
double hh = x + h - x;
return (f(x + hh) - f(x)) / hh;
}
लेकिन आपके प्लेटफॉर्म और कंपाइलर स्विच के आधार पर, उस फ़ंक्शन की दूसरी पंक्ति को आक्रामक रूप से अनुकूलन करने वाले कंपाइलर द्वारा मिटा दिया जा सकता है। इसलिए आप इसके बजाय लिखें
volatile double hh = x + h;
hh -= x;
संकलक मजबूर करने के लिए स्मृति स्थान पढ़ने के लिए hh युक्त, एक अंतिम अनुकूलन अवसर forfeiting।
h
या करने hh
में क्या अंतर है? जब hh
गणना की जाती है तो अंतिम सूत्र पहले वाले की तरह उपयोग करता है, जिसमें कोई अंतर नहीं होता है। शायद यह होना चाहिए (f(x+h) - f(x))/hh
?
h
और hh
वह यह है कि hh
ऑपरेशन से दो के कुछ नकारात्मक शक्ति को छोटा किया गया है x + h - x
। इस मामले में, x + hh
और x
बिल्कुल अलग hh
। आप अपने सूत्र को भी ले सकते हैं, यह एक ही परिणाम देगा, चूंकि x + h
और x + hh
समान हैं (यह भाजक है जो यहां महत्वपूर्ण है)।
x1=x+h; d = (f(x1)-f(x))/(x1-x)
? अस्थिर का उपयोग किए बिना।
-ffast-math
या समकक्ष।
दो उपयोग हैं। ये विशेष रूप से एम्बेडेड विकास में अधिक बार उपयोग किए जाते हैं।
कंपाइलर उन फ़ंक्शंस को ऑप्टिमाइज़ नहीं करेगा जो अस्थिर कीवर्ड के साथ परिभाषित चर का उपयोग करते हैं
वाष्पशील का उपयोग रैम, रोम, आदि में सटीक मेमोरी लोकेशन तक पहुंचने के लिए किया जाता है ... इसका उपयोग मेमोरी-मैप्ड डिवाइसों को नियंत्रित करने, सीपीयू रजिस्टरों तक पहुंचने और विशिष्ट मेमोरी स्थानों का पता लगाने के लिए अधिक बार किया जाता है।
विधानसभा सूची के साथ उदाहरण देखें। पुन :: एंबेडेड विकास में सी "अस्थिर" कीवर्ड का उपयोग
वाष्पशील भी उपयोगी है, जब आप संकलक को एक विशिष्ट कोड अनुक्रम (जैसे एक माइक्रो-बेंचमार्क लिखने के लिए) का अनुकूलन नहीं करने के लिए मजबूर करना चाहते हैं।
मैं एक और परिदृश्य का उल्लेख करूँगा जहाँ ज्वालामुखी महत्वपूर्ण हैं।
मान लीजिए कि आपने मेमोरी I-O को तेजी से I / O के लिए फाइल किया है और वह फाइल पर्दे के पीछे बदल सकती है (जैसे कि फाइल आपके स्थानीय हार्ड ड्राइव पर नहीं है, लेकिन इसके बजाय दूसरे कंप्यूटर द्वारा नेटवर्क पर सेवा की जाती है)।
यदि आप मेमोरी-मैप की गई फ़ाइल के डेटा को गैर-वाष्पशील ऑब्जेक्ट (स्रोत कोड स्तर पर) के माध्यम से एक्सेस करते हैं, तो कंपाइलर द्वारा उत्पन्न कोड आपके बारे में जागरूक हुए बिना एक ही डेटा को कई बार ला सकता है।
यदि वह डेटा बदलने के लिए होता है, तो आपका प्रोग्राम डेटा के दो या अधिक विभिन्न संस्करणों का उपयोग कर बन सकता है और असंगत स्थिति में आ सकता है। यह न केवल कार्यक्रम के तार्किक रूप से गलत व्यवहार को जन्म दे सकता है, बल्कि इसमें अविश्वसनीय सुरक्षा छेद भी कर सकता है अगर यह अविश्वासित स्थानों से अविश्वसनीय फ़ाइलों या फ़ाइलों को संसाधित करता है।
यदि आप सुरक्षा की परवाह करते हैं, और आपको करना चाहिए, तो यह विचार करने के लिए एक महत्वपूर्ण परिदृश्य है।
वाष्पशील का मतलब है कि भंडारण किसी भी समय बदलने और परिवर्तित होने की संभावना है लेकिन उपयोगकर्ता कार्यक्रम के नियंत्रण के बाहर कुछ है। इसका मतलब यह है कि यदि आप चर का संदर्भ देते हैं, तो प्रोग्राम को हमेशा भौतिक पता (यानी एक मैप किया हुआ इनपुट पेंडो) की जांच करनी चाहिए, और इसे कैश्ड तरीके से उपयोग नहीं करना चाहिए।
विकी सब कुछ के बारे में कहते हैं volatile
:
और लिनक्स कर्नेल के डॉक के बारे में भी एक उत्कृष्ट अंकन है volatile
:
मेरी राय में, आपको इससे बहुत अधिक उम्मीद नहीं करनी चाहिए volatile
। उदाहरण के लिए, निल्स पिपेनब्रिनक के अत्यधिक मत वाले उत्तर में उदाहरण देखें ।
मैं कहूंगा, उसका उदाहरण इसके लिए उपयुक्त नहीं है volatile
। volatile
इसका केवल उपयोग किया जाता है:
संकलक को उपयोगी और वांछनीय अनुकूलन करने से रोकना । यह थ्रेड सेफ, एटॉमिक एक्सेस या मेमोरी ऑर्डर के बारे में कुछ भी नहीं है।
उस उदाहरण में:
void SendCommand (volatile MyHardwareGadget * gadget, int command, int data)
{
// wait while the gadget is busy:
while (gadget->isbusy)
{
// do nothing here.
}
// set data first:
gadget->data = data;
// writing the command starts the action:
gadget->command = command;
}
gadget->data = data
पहले gadget->command = command
केवल केवल संकलक द्वारा संकलित कोड में की गारंटी है। रनिंग समय पर, प्रोसेसर अभी भी संभवतः प्रोसेसर आर्किटेक्चर के संबंध में डेटा और कमांड असाइनमेंट को फिर से व्यवस्थित करता है। हार्डवेयर गलत डेटा प्राप्त कर सकता है (मान लीजिए गैजेट को हार्डवेयर I / O में मैप किया गया है)। डेटा और कमांड असाइनमेंट के बीच मेमोरी बैरियर की जरूरत होती है।
volatile
बिना किसी कारण के प्रदर्शन को नीचा दिखा रहा है। जैसे कि यह पर्याप्त है, कि सिस्टम के अन्य पहलुओं पर निर्भर करेगा कि प्रोग्रामर को कंपाइलर के बारे में अधिक जानकारी हो सकती है। दूसरी ओर, अगर कोई प्रोसेसर गारंटी देता है कि एक निश्चित पते पर लिखने का निर्देश सीपीयू कैश को फ्लश करेगा लेकिन एक कंपाइलर ने रजिस्टर-कैश्ड वैरिएबल को फ्लश करने का कोई तरीका नहीं दिया है तो सीपीयू को कुछ भी पता नहीं है, कैश को फ्लश करना बेकार होगा।
डेनिस रिची द्वारा डिजाइन की गई भाषा में, किसी भी वस्तु तक हर पहुंच, स्वचालित वस्तुओं के अलावा जिसका पता नहीं लगा है, वह ऐसा व्यवहार करेगा जैसे कि वह वस्तु के पते की गणना करता है और फिर उस पते पर भंडारण को पढ़ा या लिखा है। इसने भाषा को बहुत शक्तिशाली बना दिया, लेकिन गंभीर रूप से सीमित अनुकूलन के अवसर।
हालांकि यह संभव हो सकता है कि एक क्वालीफ़ायर जोड़ना संभव हो जो एक संकलक को यह मानने के लिए आमंत्रित करे कि एक विशेष वस्तु को अजीब तरीकों से नहीं बदला जाएगा, इस तरह की धारणा सी कार्यक्रमों में वस्तुओं के विशाल बहुमत के लिए उपयुक्त होगी, और यह होगा उन सभी वस्तुओं के लिए एक क्वालीफायर जोड़ना अव्यावहारिक है, जिसके लिए ऐसी धारणा उचित होगी। दूसरी ओर, कुछ कार्यक्रमों को कुछ वस्तुओं का उपयोग करने की आवश्यकता होती है, जिसके लिए ऐसी धारणा धारण नहीं करेगी। इस समस्या को हल करने के लिए, मानक का कहना है कि संकलक मान सकते हैं कि जिन वस्तुओं को घोषित volatile
नहीं किया गया है, उनका मान मनाया नहीं जाएगा या उन तरीकों से नहीं बदला जाएगा जो संकलक के नियंत्रण के बाहर हैं, या एक उचित संकलक की समझ के बाहर होंगे।
क्योंकि विभिन्न प्लेटफार्मों में अलग-अलग तरीके हो सकते हैं जिनमें ऑब्जेक्ट्स को संकलक के नियंत्रण के बाहर देखा या संशोधित किया जा सकता है, यह उचित है कि उन प्लेटफार्मों के लिए गुणवत्ता संकलक उनके सटीक हैंडलिंग में भिन्न हों volatile
शब्दार्थों के । दुर्भाग्य से, क्योंकि मानक यह सुझाव देने में विफल रहा कि किसी प्लेटफ़ॉर्म पर निम्न-स्तरीय प्रोग्रामिंग के लिए इच्छित गुणवत्ता संकलक volatile
को इस तरह से संभालना चाहिए जो उस प्लेटफ़ॉर्म पर किसी विशेष रीड / राइट ऑपरेशन के किसी भी और सभी प्रासंगिक प्रभावों को पहचान लेगा, कई कंपाइलर करने से कम हो जाते हैं इसलिए उन तरीकों से, जो पृष्ठभूमि I / O जैसी चीजों को संसाधित करना कठिन बनाते हैं, जो कुशल है, लेकिन संकलक / अनुकूलन द्वारा नहीं तोड़ा जा सकता है।
सरल शब्दों में, यह संकलक को किसी विशेष चर पर कोई अनुकूलन नहीं करने के लिए कहता है। चर जिन्हें डिवाइस रजिस्टर में मैप किया जाता है उन्हें डिवाइस द्वारा अप्रत्यक्ष रूप से संशोधित किया जाता है। इस मामले में, अस्थिर का उपयोग किया जाना चाहिए।
संकलित कोड के बाहर से एक वाष्पशील को बदला जा सकता है (उदाहरण के लिए, एक प्रोग्राम एक अस्थिर चर को मेमोरी मैप्ड रजिस्टर में मैप कर सकता है।) संकलक कोड में कुछ अनुकूलन लागू नहीं करेगा जो एक अस्थिर चर को संभालता है - उदाहरण के लिए, यह जीता है ' t इसे मेमोरी में लिखे बिना एक रजिस्टर में लोड करें। हार्डवेयर रजिस्टरों से निपटने के दौरान यह महत्वपूर्ण है।
जैसा कि यहां कई लोगों द्वारा सुझाया गया है, वाष्पशील कीवर्ड का लोकप्रिय उपयोग वाष्पशील चर के अनुकूलन को छोड़ना है।
सबसे अच्छा फायदा जो दिमाग में आता है, और अस्थिरता के बारे में पढ़ने के बाद ध्यान देने योग्य है - एक के मामले में चर के वापस रोलिंग को रोकने के लिए longjmp
। एक गैर-स्थानीय कूद।
इसका क्या मतलब है?
इसका सीधा सा मतलब है कि स्टैक अनइंडिंग करने के बाद अंतिम मूल्य बरकरार रखा जाएगा , कुछ पिछले स्टैक फ्रेम पर लौटने के लिए; आमतौर पर कुछ गलत परिदृश्य के मामले में।
चूँकि यह इस प्रश्न के दायरे से बाहर होगा, मैं setjmp/longjmp
यहाँ विवरण में नहीं जा रहा हूँ , लेकिन इसके बारे में पढ़ने लायक है; और अंतिम मूल्य को बनाए रखने के लिए अस्थिरता सुविधा का उपयोग कैसे किया जा सकता है।