سؤال

ماذا يكون ال volatile الكلمة الرئيسية تفعل؟في C++ ما هي المشكلة التي يحلها؟

وفي حالتي، لم أكن في حاجة إليها عن قصد.

هل كانت مفيدة؟

المحلول

volatile هناك حاجة إليه إذا كنت تقرأ من مكان في الذاكرة، على سبيل المثال، عملية/جهاز/أي شيء قد يكتب إليه منفصل تمامًا.

كنت أعمل مع ذاكرة الوصول العشوائي ثنائية المنفذ في نظام متعدد المعالجات في C على التوالي.استخدمنا جهازًا مُدارًا بقيمة 16 بت كإشارة لمعرفة متى انتهى الشخص الآخر.لقد فعلنا هذا بشكل أساسي:

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

بدون volatile, ، يرى المُحسِّن أن الحلقة عديمة الفائدة (الرجل لا يحدد القيمة أبدًا!إنه مجنون، تخلص من هذا الرمز!) وسيستمر الكود الخاص بي دون الحصول على الإشارة، مما يسبب مشاكل لاحقًا.

نصائح أخرى

volatile تكون ضرورية عند تطوير الأنظمة المضمنة أو برامج تشغيل الأجهزة، حيث تحتاج إلى قراءة أو كتابة جهاز معيّن للذاكرة.يمكن أن تتغير محتويات سجل جهاز معين في أي وقت، لذلك تحتاج إلى volatile الكلمة الأساسية للتأكد من أن عمليات الوصول هذه لم يتم تحسينها بواسطة المترجم.

تحتوي بعض المعالجات على سجلات الفاصلة العائمة التي تحتوي على أكثر من 64 بت من الدقة (على سبيل المثال.32 بت x86 بدون SSE، راجع تعليق بيتر).وبهذه الطريقة، إذا قمت بتشغيل عدة عمليات على أرقام مزدوجة الدقة، فستحصل في الواقع على إجابة أعلى دقة مما لو كنت تريد اقتطاع كل نتيجة وسيطة إلى 64 بت.

يعد هذا أمرًا رائعًا عادةً، ولكنه يعني أنه اعتمادًا على كيفية تعيين المترجم للسجلات وإجراء التحسينات، سيكون لديك نتائج مختلفة لنفس العمليات بالضبط على نفس المدخلات بالضبط.إذا كنت بحاجة إلى الاتساق، فيمكنك إجبار كل عملية على العودة إلى الذاكرة باستخدام الكلمة الأساسية المتقلبة.

كما أنها مفيدة لبعض الخوارزميات التي ليس لها أي معنى جبري ولكنها تقلل من خطأ النقطة العائمة، مثل جمع كاهان.جبريًا، هذا أمر لا، لذلك غالبًا ما يتم تحسينه بشكل غير صحيح ما لم تكن بعض المتغيرات الوسيطة متقلبة.

من "متقلب كوعد" مقال بقلم دان ساكس:

(...) الشيء المتطاير هو الذي قد تتغير قيمته تلقائيا.وهذا يعني أنه عندما تعلن عن كائن متطاير، فإنك تخبر المترجم أن الكائن قد يغير حالته على الرغم من عدم ظهور أي عبارات في البرنامج لتغييره.

فيما يلي روابط لثلاثة من مقالاته المتعلقة volatile الكلمة الرئيسية:

يجب عليك استخدام المتقلبة عند تنفيذ هياكل البيانات الخالية من القفل.وإلا فإن المترجم حر في تحسين الوصول إلى المتغير، مما سيؤدي إلى تغيير الدلالات.

وبعبارة أخرى، يخبر المتغير المترجم أن الوصول إلى هذا المتغير يجب أن يتوافق مع عملية القراءة/الكتابة في الذاكرة الفعلية.

على سبيل المثال، هذه هي الطريقة التي يتم بها الإعلان عن InterlockedIncrement في Win32 API:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

هناك تطبيق كبير اعتدت العمل عليه في أوائل التسعينيات يحتوي على معالجة الاستثناءات المستندة إلى لغة C باستخدام setjmp وlongjmp.كانت الكلمة الأساسية المتقلبة ضرورية للمتغيرات التي يلزم الحفاظ على قيمها في كتلة التعليمات البرمجية التي كانت بمثابة عبارة "catch"، خشية أن يتم تخزين تلك vars في السجلات ومحوها بواسطة longjmp.

في المعيار C، أحد الأماكن التي يمكن استخدامها volatile مع معالج الإشارة.في الواقع، في المعيار C، كل ما يمكنك القيام به بأمان في معالج الإشارة هو تعديل أ volatile sig_atomic_t متغير، أو الخروج بسرعة.في الواقع، AFAIK، هو المكان الوحيد في المعيار C الذي يتم استخدامه volatile مطلوب لتجنب السلوك غير المحدد.

ISO/IEC 9899:2011 §7.14.1.1 signal وظيفة

¶5 إذا حدثت الإشارة بخلاف نتيجة الاتصال abort أو raise الوظيفة ، يكون السلوك غير محدد إذا كان معالج الإشارة يشير إلى أي كائن له مدة تخزين ثابت أو مؤشر ترابط ليس كائنًا ذريًا خاليًا volatile sig_atomic_t, ، أو معالج الإشارة يدعو أي وظيفة في المكتبة القياسية بخلاف abort وظيفة، _Exit وظيفة، quick_exit الوظيفة، أو signal وظيفة مع الوسيطة الأولى مساوية لرقم الإشارة المقابل للإشارة التي تسببت في استدعاء المعالج.علاوة على ذلك، إذا كانت هذه الدعوة إلى signal تؤدي الوظيفة إلى عودة sig_err ، قيمة errno غير محدد.252)

252) إذا تم إنشاء أي إشارة بواسطة معالج إشارة غير متزامن، يكون السلوك غير محدد.

وهذا يعني أنه في المعيار C، يمكنك كتابة:

static volatile sig_atomic_t sig_num = 0;

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

وليس الكثير غير ذلك.

يعتبر POSIX أكثر تساهلاً فيما يتعلق بما يمكنك القيام به في معالج الإشارات، ولكن لا تزال هناك قيود (وأحد القيود هو أن مكتبة الإدخال/الإخراج القياسية — printf() وآخرون - لا يمكن استخدامها بأمان).

أثناء التطوير لجزء مضمن، لدي حلقة تتحقق من متغير يمكن تغييره في معالج المقاطعة.بدون "متقلبة"، تصبح الحلقة noop - بقدر ما يستطيع المترجم أن يقول، فإن المتغير لا يتغير أبدًا، لذلك فهو يعمل على تحسين عملية التحقق.

ينطبق الشيء نفسه على متغير يمكن تغييره في سلسلة رسائل مختلفة في بيئة أكثر تقليدية، ولكننا غالبًا ما نقوم بإجراء مكالمات مزامنة، لذلك لا يكون المترجم حرًا في التحسين.

لقد استخدمته في تصميمات تصحيح الأخطاء عندما يصر المترجم على تحسين المتغير الذي أريد أن أتمكن من رؤيته أثناء التنقل خلال التعليمات البرمجية.

إلى جانب استخدامه على النحو المنشود، يتم استخدام المتقلب في البرمجة الوصفية (القالب).يمكن استخدامه لمنع التحميل الزائد العرضي، حيث تشارك السمة المتغيرة (مثل const) في تحليل التحميل الزائد.

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); }
};

هذا قانوني؛من المحتمل أن يكون كلا التحميلين الزائدين قابلين للاستدعاء ويفعلان نفس الشيء تقريبًا.طاقم الممثلين في volatile التحميل الزائد أمر قانوني لأننا نعلم أن الشريط لن يمر بحالة غير متطايرة T على أي حال.ال volatile الإصدار أسوأ تمامًا، لذلك لم يتم اختياره مطلقًا في دقة التحميل الزائد إذا كان غير متطاير f متاح.

لاحظ أن الكود لا يعتمد عليه أبدًا volatile الوصول إلى الذاكرة.

  1. يجب عليك استخدامه لتنفيذ Spinlocks بالإضافة إلى بعض هياكل البيانات الخالية من القفل (الكل؟).
  2. استخدامه مع العمليات/التعليمات الذرية
  3. ساعدني مرة واحدة في التغلب على خطأ المترجم (الكود الذي تم إنشاؤه بشكل خاطئ أثناء التحسين)

ال volatile تهدف الكلمة الأساسية إلى منع المترجم من تطبيق أي تحسينات على الكائنات التي يمكن أن تتغير بطرق لا يمكن للمترجم تحديدها.

الكائنات المعلنة على أنها volatile تم حذفها من التحسين لأنه يمكن تغيير قيمها عن طريق التعليمات البرمجية خارج نطاق التعليمات البرمجية الحالية في أي وقت.يقرأ النظام دائمًا القيمة الحالية لـ a volatile الكائن من موقع الذاكرة بدلاً من الاحتفاظ بقيمته في السجل المؤقت عند النقطة المطلوبة، حتى لو طلبت تعليمات سابقة قيمة من نفس الكائن.

النظر في الحالات التالية

1) المتغيرات العامة المعدلة بواسطة روتين خدمة المقاطعة خارج النطاق.

2) المتغيرات العالمية داخل تطبيق متعدد الخيوط.

إذا لم نستخدم المؤهل المتقلب، فقد تنشأ المشاكل التالية

1) قد لا تعمل التعليمات البرمجية كما هو متوقع عند تشغيل التحسين.

2) قد لا يعمل الرمز كما هو متوقع عند تمكين المقاطعات واستخدامها.

متقلب:أفضل صديق للمبرمج

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

إلى جانب حقيقة أن الكلمة الأساسية المتغيرة تُستخدم لإخبار المترجم بعدم تحسين الوصول إلى بعض المتغيرات (التي يمكن تعديلها بواسطة مؤشر ترابط أو روتين مقاطعة)، يمكن أيضًا أن تكون كذلك يستخدم لإزالة بعض أخطاء المترجم -- نعم يمكن أن يكون ---.

على سبيل المثال، عملت على منصة مضمنة حيث كان المترجم يقوم ببعض الافتراضات الخاطئة فيما يتعلق بقيمة المتغير.إذا لم يتم تحسين الكود، فسيعمل البرنامج بشكل جيد.مع التحسينات (التي كانت مطلوبة حقًا لأنها كانت روتينًا بالغ الأهمية) لن تعمل التعليمات البرمجية بشكل صحيح.كان الحل الوحيد (وإن لم يكن صحيحًا تمامًا) هو إعلان المتغير "المعيب" على أنه متقلب.

يبدو أن برنامجك يعمل حتى بدونه volatile الكلمة الرئيسية؟ربما هذا هو السبب:

كما ذكرنا سابقا volatile الكلمات الرئيسية تساعد في حالات مثل

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

ولكن يبدو أنه لا يوجد أي تأثير تقريبًا بمجرد استدعاء دالة خارجية أو غير مضمنة.على سبيل المثال:

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

ثم مع أو بدون volatile يتم إنشاء نفس النتيجة تقريبًا.

طالما يمكن تضمين g() بالكامل، يمكن للمترجم رؤية كل ما يحدث وبالتالي يمكنه تحسينه.ولكن عندما يقوم البرنامج باستدعاء مكان لا يتمكن فيه المترجم من رؤية ما يحدث، فليس من الآمن للمترجم أن يقوم بأي افتراضات بعد الآن.ومن ثم سيقوم المترجم بإنشاء كود يُقرأ دائمًا من الذاكرة مباشرة.

لكن احذر من اليوم، عندما تصبح وظيفتك g() مضمّنة (إما بسبب تغييرات صريحة أو بسبب ذكاء المترجم/الرابط)، فقد تنكسر التعليمات البرمجية الخاصة بك إذا نسيت volatile الكلمة الرئيسية!

لذلك أوصي بإضافة volatile الكلمة الأساسية حتى لو بدا أن برنامجك يعمل بدونها.فهو يجعل النية أكثر وضوحا وأكثر قوة فيما يتعلق بالتغيرات المستقبلية.

في الأيام الأولى للغة C، كان المترجمون يفسرون جميع الإجراءات التي تقرأ وتكتب القيم على أنها عمليات ذاكرة، يتم تنفيذها بنفس التسلسل الذي تظهر فيه عمليات القراءة والكتابة في الكود.يمكن تحسين الكفاءة بشكل كبير في العديد من الحالات إذا تم منح المترجمين قدرًا معينًا من الحرية لإعادة ترتيب العمليات ودمجها، ولكن كانت هناك مشكلة في ذلك.حتى العمليات غالبًا ما يتم تحديدها بترتيب معين لمجرد أنه كان من الضروري تحديدها بعض النظام، وبالتالي اختار المبرمج أحد البدائل العديدة المتساوية في الجودة، لم يكن هذا هو الحال دائمًا.في بعض الأحيان يكون من المهم أن تتم عمليات معينة في تسلسل معين.

ستختلف تفاصيل التسلسل المهمة اعتمادًا على النظام الأساسي المستهدف ومجال التطبيق.بدلاً من توفير تحكم مفصل بشكل خاص، اختار المعيار نموذجًا بسيطًا:إذا تم تنفيذ سلسلة من عمليات الوصول باستخدام قيم غير مؤهلة volatile, ، يجوز للمترجم إعادة ترتيبها ودمجها كما يراها مناسبة.إذا تم إجراء مع volatile-القيمة المؤهلة، يجب أن يقدم تنفيذ الجودة أي ضمانات طلب إضافية قد تكون مطلوبة من خلال التعليمات البرمجية التي تستهدف النظام الأساسي ومجال التطبيق المقصود، دون الحاجة إلى طلب استخدام تركيب غير قياسي.

لسوء الحظ، بدلاً من تحديد الضمانات التي يحتاجها المبرمجون، اختار العديد من المترجمين بدلاً من ذلك تقديم الحد الأدنى من الضمانات التي يفرضها المعيار.هذا يجعل volatile أقل فائدة بكثير مما ينبغي.في gcc أو clang، على سبيل المثال، يحتاج المبرمج إلى تنفيذ عملية "hand-off mutex" الأساسية [حيث لن تقوم المهمة التي اكتسبت كائن المزامنة وأصدرته بذلك مرة أخرى حتى تنتهي المهمة الأخرى] يجب عليه القيام بذلك من أربعة أشياء:

  1. ضع عملية الحصول على كائن المزامنة (mutex) وإصداره في وظيفة لا يستطيع المترجم تضمينها، ولا يمكنه تطبيق تحسين البرنامج بالكامل عليها.

  2. تأهيل كافة الكائنات التي يحرسها كائن المزامنة كـ volatile--شيء لا ينبغي أن يكون ضروريًا إذا حدثت جميع عمليات الوصول بعد الحصول على كائن المزامنة وقبل إطلاقه.

  3. استخدم مستوى التحسين 0 لإجبار المترجم على إنشاء تعليمات برمجية كما لو كانت جميع الكائنات غير مؤهلة register نكون volatile.

  4. استخدم التوجيهات الخاصة بدول مجلس التعاون الخليجي.

على النقيض من ذلك، عند استخدام مترجم عالي الجودة وأكثر ملاءمة لبرمجة الأنظمة، مثل icc، سيكون لدى المرء خيار آخر:

  1. تأكد من أن أ volatile-يتم تنفيذ الكتابة المؤهلة في كل مكان يلزم فيه الحصول على أو إصدار.

يتطلب الحصول على "كائن المزامنة" الأساسي وجود volatile read (لمعرفة ما إذا كان جاهزًا)، ولا ينبغي أن يتطلب ملف volatile الكتابة أيضًا (لن يحاول الجانب الآخر إعادة الحصول عليها حتى يتم إعادتها) ولكن يتعين عليه إجراء عملية لا معنى لها volatile write لا يزال أفضل من أي من الخيارات المتاحة ضمن gcc أو clang.

أحد الاستخدامات التي يجب أن أذكرك بها هو أنه في وظيفة معالج الإشارة، إذا كنت تريد الوصول إلى/تعديل متغير عام (على سبيل المثال، وضع علامة عليه كـ "خروج = صحيح")، فيجب عليك إعلان هذا المتغير على أنه "متقلب".

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top