أين يتم تخزين الثوابت على متحكم CortexM (باستخدام برنامج التحويل البرمجي C ++ IAR كمثال)

أقوم بتدريس طلابي كيفية استخدام متحكم STM32F411RE ، الذي يحتوي على 512 كيلو بايت من ذاكرة الوصول العشوائي و 128 كيلو بايت من ذاكرة الوصول العشوائي
عادةً ما يكون هذا البرنامج متحكمًا على ذاكرة الوصول العشوائي ، وفي ذاكرة الوصول العشوائي تكون البيانات المراد تغييرها ضرورية جدًا لجعل الثوابت في ذاكرة القراءة فقط .
في STM32F411RE ، متحكم ROM ، توجد الذاكرة في العناوين ذات 0x08000000 ... 0x0807FFFF ، وذاكرة الوصول العشوائي مع 0x20000000 ... 0x2001FFFF.

وإذا كانت جميع إعدادات الرابط صحيحة ، فإن الطالب يحسب أنه في مثل هذا الكود المباشر يكمن ثابته في ROM :

class WantToBeInROM { private: int i; public: WantToBeInROM(int value): i(value) {} int Get() const { return i; } }; const WantToBeInROM myConstInROM(10); int main() { std::cout << &myConstInROM << std::endl ; } 

يمكنك أيضًا محاولة الإجابة على السؤال: أين هو myConstInROM الثابت في ROM أو في RAM ؟

إذا أجبت على هذا السؤال ، فأنا أهنئك في ROM ، في الواقع على الأرجح أنك مخطئ ، فإن الثابت يكمن بشكل عام في ذاكرة الوصول العشوائي ومعرفة كيفية وضع ثوابتك بشكل صحيح وصحيح في ROM - مرحبًا بك في cat.

مقدمة


أولاً ، استطراد صغير ، لماذا تهتم على الإطلاق بهذا.
عند تطوير برنامج أمان مهم لقياس الأجهزة التي تتوافق مع IEC 61508-3: 2010 أو ما يعادلها المحلي لـ GOST IEC 61508-3-2018 ، يجب أخذ عدد من النقاط في الاعتبار ليست مهمة للبرامج التقليدية.

الرسالة الرئيسية لهذا المعيار هي أن البرنامج يجب أن يكتشف أي فشل يؤثر على موثوقية النظام ويضع النظام في وضع "تعطل"

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

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

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

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

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

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

لذلك ، فإن أسهل طريقة هي التأكد من أن البيانات الثابتة في ROM 100٪. كيفية القيام بذلك أريد أن أحاول أن أشرح. لكن عليك أولاً أن تتحدث عن تنظيم الذاكرة في أرمينيا.

تنظيم الذاكرة


كما تعلمون ، يحتوي ARM core على هندسة Harvard - يتم فصل حافلات البيانات والرمز. عادةً ما يعني ذلك وجود ذاكرة منفصلة للبرامج وذاكرة منفصلة للبيانات. ولكن الحقيقة هي أن ARM هي بنية معدلة بجامعة هارفارد ، أي يتم الوصول إلى الذاكرة في حافلة واحدة ، ويوفر جهاز إدارة الذاكرة بالفعل فصل الحافلات باستخدام إشارات التحكم: قراءة أو كتابة أو تحديد منطقة الذاكرة.

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

لذلك ، من أجل التمييز بين مناطق الذاكرة لـ ROM (Flash) وذاكرة الوصول العشوائي (RAM) ، تتم الإشارة إليها عادةً في إعدادات الرابط ، على سبيل المثال ، في IAR 8.40.1 ، تبدو كما يلي:

 define symbol __ICFEDIT_region_ROM_start__ = 0x08000000; define symbol __ICFEDIT_region_ROM_end__ = 0x0807FFFF; define symbol __ICFEDIT_region_RAM_start__ = 0x20000000; define symbol __ICFEDIT_region_RAM_end__ = 0x2001FFFF; define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__]; define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__]; 

يقع RAM في هذا متحكم في 0x20000000 ... 0x2001FFF ، و ROM في 0x008000000 ... 0x0807FFFF .
يمكنك بسهولة تغيير عنوان البدء ROM_start إلى عنوان RAM ، ويقول RAM_start وعنوان النهاية ROM_end__ إلى RAM_end__ وسيكون برنامجك موجودًا بالكامل في RAM.
يمكنك القيام بالعكس وتحديد ذاكرة الوصول العشوائي في مساحة ذاكرة ROM ، وسيعمل البرنامج على التجميع والفلاش بنجاح ، على الرغم من أنه لن يعمل :)
تحتوي بعض المتحكمات الدقيقة ، مثل AVR ، في البداية على مساحة عنوان منفصلة لذاكرة البرنامج وذاكرة البيانات والأجهزة الطرفية ، وبالتالي لن تعمل هذه الحيل هناك ، ويتم كتابة البرنامج على ROM افتراضيًا.

كل مساحة العنوان في CortexM واحدة ، ويمكن العثور على الكود والبيانات في أي مكان. باستخدام إعدادات رابط ، يمكنك تعيين المنطقة لعناوين ROM و RAM . يحدد موقع IAR مقطع شفرة النص. في منطقة ROM

ملف الكائن وشرائح


أعلاه ، ذكرت مقطع الكود ، دعنا نرى ما هو عليه.

يتم إنشاء ملف كائن منفصل لكل وحدة نمطية مترجمة ، والتي تحتوي على المعلومات التالية:

  • شرائح البيانات والبيانات
  • معلومات تصحيح DWARF
  • جدول الشخصيات

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

  • رمز - رمز قابل للتنفيذ
  • للقراءة فقط - متغيرات ثابتة
  • قراءة - المتغيرات تهيئة
  • zeroinit - صفر تهيئة المتغيرات

بالطبع ، هناك أنواع أخرى من الأجزاء ، على سبيل المثال الأجزاء التي تحتوي على معلومات تصحيح الأخطاء ، لكننا سنهتم فقط بتلك التي تحتوي على كود أو بيانات من تطبيقنا.

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

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

  • .bss - يحتوي على متغيرات ثابتة وعالمية تتم تهيئتها إلى 0
  • .CSTACK - يحتوي على المكدس المستخدم من قبل البرنامج
  • .data - يحتوي على متغيرات تهيئة ثابتة وعالمية
  • .data_init - يحتوي على القيم الأولية للبيانات في قسم .data إذا تم استخدام توجيه التهيئة للرابط
  • HEAP - يحتوي على كومة الذاكرة المؤقتة المستخدمة لاستضافة البيانات الحيوية
  • .intvec - يحتوي على جدول متجه المقاطعة
  • .rodata - يحتوي على بيانات ثابتة
  • .text - يحتوي على رمز البرنامج

لفهم مكان الثوابت ، سنهتم فقط بالقطاعات
.rodata - قطعة يتم تخزين الثوابت فيها ،
.data - قطعة يتم فيها تخزين كل المتغيرات الثابتة والعامة التي تم تهيئتها ،
.bss - قطعة يتم فيها تخزين كل متغيرات .dat الثابتة والعامة التي تم تهيئتها بصفر (0) ،
.text - قطعة لتخزين التعليمات البرمجية.

في الممارسة العملية ، هذا يعني أنه إذا قمت بتعريف المتغير int val = 3 ، فسيتم وضع المتغير نفسه بواسطة برنامج التحويل البرمجي في قطعة .data ووضع علامة عليه مع سمة readwrite ، ويمكن وضع الرقم 3 إما في مقطع .text أو في مقطع .rodata أو يتم تطبيق توجيه خاص للرابط في .data_init كما تم وضع علامة عليه للقراءة فقط .

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

الآن يصبح من الواضح ما هو موصوف في إعدادات الرابط ولماذا:

 place in ROM_region { readonly }; //   .rodata  .data_init (  )  ROM: place in RAM_region { readwrite, //   .data, .bss,  .noinit block STACK }; //  STACK  HEAP  RAM 

أي ، يجب وضع جميع البيانات المميزة بالسمة للقراءة فقط في ROM_region. وبالتالي ، يمكن الوصول إلى ROM من البيانات من قطاعات مختلفة ، ولكن تم تمييزها بالخاصية للقراءة فقط.

حسنًا ، هذا يعني أن جميع الثوابت يجب أن تكون في ذاكرة الوصول العشوائي ، ولكن لماذا في الكود ، في بداية المقالة ، هل ما زال الكائن الثابت في ذاكرة الوصول العشوائي؟
 class WantToBeInROM { private: int i; public: WantToBeInROM(int value): i(value) {} int Get() const { return i; } }; const WantToBeInROM myConstInROM(10); int main() { std::cout << &myConstInROM << std::endl ; } 



بيانات ثابتة


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

ماذا يعني هذا في C ++. لنلقِ نظرة على مثال:

 void foo(const int& C1, const int& C2, const int& C3, const int& C4, const int& C5, const int& C6) { std::cout << C1 << C2 << C3 << C4 << C5 << C6 << std::endl; } //     constexpr int Case1 = 1 ; //  (      ) const int Case2 = 2; int main() { //  . const int Case3 = 3 ; // . static const int Case4 = 4 ; //      ,     . constexpr int Case5 = Case1 + 5 ; //     . static constexpr int Case6 = 6 ; foo(Case1,Case2,Case3,Case4,Case5,Case6); return 1; } 

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

  • يجب أن يكون ثابت الحالة Case1 في ROM . في الجزء .rodata
  • يجب أن يكون ثابت الحالة Case2 في ROM . في الجزء .rodata
  • يجب أن يكون ثابت الحالة المحلية Case3 في ذاكرة الوصول العشوائي (تم إنشاء الثابت على المكدس في مقطع STACK)
  • يجب أن يكون ثابت ثابت Case4 في ROM . في الجزء .rodata
  • يجب أن يكون الثابت المحلي لـ Case5 في ذاكرة الوصول العشوائي (وهي حالة مثيرة للاهتمام ، ولكنها مطابقة تمامًا للحالة 3.)
  • يجب أن يكون ثابت ثابت Case6 في ROM . في الجزء .rodata

الآن دعونا نلقي نظرة على معلومات تصحيح الأخطاء وملف الخريطة الذي تم إنشاؤه. يعرض المصحح عناوين هذه الثوابت الموجودة في.

صورة

كما قلت من قبل ، العناوين 0x0800 ... هذه هي عناوين ROM ، و 0x200 ... هذه هي ذاكرة الوصول العشوائي . لنرى في الأجزاء التي قام المترجم بتوزيع هذه الثوابت بها:

  .rodata const 0x800'4e2c 0x4 main.o //Case1 .rodata const 0x800'4e30 0x4 main.o //Case2 .rodata const 0x800'4e34 0x4 main.o //Case4 .rodata const 0x800'4e38 0x4 main.o //Case6 

سقطت ثوابت عمومية وثابتة في مقطع .rodata ، ولم يقع متغيرين محليين في ملف الخريطة لأنهما تم إنشاؤهما على الحزمة وأن عنوانهما يتوافق مع عناوين الحزمة. يبدأ الجزء CSTACK في 0x2000'2488 وينتهي في 0x2000'0488. كما ترون من الصورة ، يتم إنشاء الثوابت فقط في بداية المكدس.

يضع المترجم ثوابت عمومية وثابتة في مقطع .rodata ، حيث يتم تحديد موقعه في إعدادات رابط.

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

على سبيل المثال ، إذا تم تعريف المتغير العام int i = 3 ، فإن المترجم حدده في مقطع بيانات .data ، وضعه الرابط في 0x20000000:
.data inited 0x2000'0000 ،
وستقع قيمة التهيئة الخاصة به (3) في مقطع .rodata على العنوان 0x8000190:
Initializer bytes const 0x800'0190
إذا كتبت هذا الكود:

 int i = 3; const int c = i; 

من الواضح أن الثابت العام ، لا تتم تهيئته إلا بعد تهيئة المتغير الشامل i ، أي في وقت التشغيل. في هذه الحالة ، سيكون ثابت في ذاكرة الوصول العشوائي

الآن إذا عدنا إلى موقعنا
المثال الأولي
 class WantToBeInROM { private: int i; public: WantToBeInROM(int value): i(value) {} int Get() const { return i; } }; const WantToBeInROM myConstInROM(10); int main() { std::cout << &myConstInROM << std::endl ; } 

ونسأل أنفسنا: في أي جزء قام المترجم بتعريف الكائن الثابت myConstInROM ؟ ونحصل على الإجابة: الثابت في الجزء .bss ، الذي يحتوي على متغيرات ثابتة وعالمية تمت تهيئتها إلى صفر (0).
.bss inited 0x2000'0004 0x4
myConstInROM 0x2000'0004 0x4


لماذا؟ لأنه في C ++ ، يوجد كائن بيانات تم إعلانه على أنه ثابت ويحتاج إلى تهيئة ديناميكية في ذاكرة القراءة والكتابة وسيتم تهيئته في وقت الإنشاء ..

في هذه الحالة ، يحدث التهيئة الديناميكية ، const WantToBeInROM myConstInROM(10) ، ويقوم المترجم بوضع هذا الكائن في مقطع .bss ، تهيئة كل الحقول 0 أولاً ، ثم ، عند إنشاء كائن ثابت ، يسمى المنشئ لتهيئة الحقل i بالقيمة 10.

كيف يمكننا أن نجعل المترجم مكان كائن لدينا في قطعة .rodata ؟ إجابة هذا السؤال بسيطة ، يجب عليك دائمًا إجراء التهيئة الثابتة. يمكنك القيام بذلك بهذه الطريقة:

1. في مثالنا ، يمكن ملاحظة أن المترجم يمكنه ، من حيث المبدأ ، تحسين التهيئة الديناميكية للاستاتيكية ، لأن المُنشئ بسيط للغاية. بالنسبة إلى IAR الخاص بالبرنامج المترجم ، يمكنك وضع علامة الثابت بالسمة __ro_placement
__ro_placement const WantToBeInROM myConstInROM
باستخدام هذا الخيار ، سيقوم المترجم بوضع المتغير على العنوان في ROM:
myConstInROM 0x800'0144 0x4 Data
من الواضح أن هذا النهج ليس شاملاً وعمومًا شديد التحديد. لذلك ، ننتقل إلى الطريقة الصحيحة :)

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

كائن يطير إلى ROM
 class WantToBeInROM { private: int i; public: constexpr WantToBeInROM(int value): i(value) {} int Get() const { return i; } }; const WantToBeInROM myConstInROM(10); int main() { std::cout << &myConstInROM << std::endl ; } 


لذلك ، للتأكد من وجود كائن ثابت في ROM ، يجب عليك اتباع قواعد بسيطة:
  1. يجب أن تكون قطعة النص. التي يتم وضع الكود عليها في ROM. تم تكوينه في إعدادات رابط.
  2. يجب أن تكون قطعة .rodata التي توجد بها ثوابت عالمية وثابتة في ROM. تم تكوينه في إعدادات رابط.
  3. يجب أن يكون ثابت عالمي أو ثابت.
  4. يجب ألا تكون سمات فئة متغير ثابت قابلة للتغيير
  5. يجب أن تكون تهيئة الكائن ثابتة ، أي أن مُنشئ الفئة التي سيكون كائنها ثابتًا يجب أن يكون constexpr أو غير محدد على الإطلاق (لا يوجد تهيئة ديناميكية)
  6. إذا كان ذلك ممكنًا ، إذا كنت متأكدًا من أنه ينبغي تخزين الكائن في ROM بدلاً من const ، استخدم constexpr

بضع كلمات عن constexpr ومنشئ constexpr. الفرق الرئيسي بين const و constexpr هو أن تهيئة متغير const يمكن أن تتأخر حتى وقت التشغيل. يجب تهيئة متغير constexpr في وقت الترجمة.
جميع المتغيرات constexpr هي من النوع const.

يجب أن يفي تعريف مُنشئ constexpr بالمتطلبات التالية:
  • يجب ألا تحتوي الفئة على فئات أساسية افتراضية.
     struct D2 : virtual BASE { //error, D2 must not have virtual base class. constexpr D2() : BASE(), mem(55) { } private: int mem; }; 
  • يجب أن يكون كل نوع من أنواع المعلمات في الفصل نوعًا حرفيًا.
  • يجب أن يكون نص المنشئ = delete أو = default . أو استيفاء المتطلبات أدناه:
  • لا توجد أي try catch الكتل في جسم المنشئ.
  • يمكن للجسم nullptr استخدام nullptr
  • يمكن لجسم static_assert استخدام static_assert
  • في نص المنشئ ، يمكن استخدام typedef التي لا تحدد الفئات أو التعدادات
  • يمكن للجسم المنشئ استخدام التوجيه والإعلانات using
  • يجب تهيئة كل عضو غير ثابت في فئة أو فئة أساسية.
  • يجب أن يكون constexpr الفئة أو الفئة الأساسية ، المُستخدَمين لتهيئة العناصر غير الثابتة لأعضاء الفصل constexpr للفئة الأساسية ، هم constexpr .
  • يجب أن تكون constexpr عناصر البيانات غير الثابتة هي constexpr
  • عند تهيئة أعضاء الفصل ، يجب أن تكون جميع تحويلات الكتابة صالحة في تعبير ثابت. على سبيل المثال ، لا يُسمح باستخدام reinterpret_cast والإرسال من void* إلى نوع آخر من المؤشر

المُنشئ الافتراضي الضمني هو مُنشئ constexpr. الآن دعونا نلقي نظرة على بعض الأمثلة:

مثال 1. كائن في ROM
 class Test { private: int i; public: Test() {} ; int Get() const { return i + 1; } } ; const Test test; //  ROM.    . i  0  . int main() { std::cout << test.Get() << std::endl ; return 0; } 


من الأفضل عدم الكتابة بهذه الطريقة ، لأنه بمجرد أن تقرر تهيئة السمة i ، سينتقل الكائن إلى RAM

مثال 2. كائن في ذاكرة الوصول العشوائي
 class Test { private: int i = 1; // i.       constexpr . public: Test() {} ; //       ,       ,  constexpr int Get() const { return i + 1; } } ; const Test test; //  RAM. i     int main() { std::cout << test.Get() << std::endl ; return 0; } 



مثال 3. كائن في ذاكرة الوصول العشوائي
 class Test { private: int i; public: Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; const Test test(10); //  RAM. i     int main() { std::cout << test.Get() << std::endl ; return 0; } 



مثال 4. كائن في ROM
 class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; const Test test(10); //  ROM. i     constexpr  int main() { std::cout << test.Get() << std::endl ; return 0; } 



مثال 5. كائن في RAM
 class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; int main() { const Test test(10); //  RAM.    std::cout << test.Get() << std::endl ; return 0; } 



مثال 6. كائن في ROM
 class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() const { return i + 1; } } ; int main() { static const Test test(10); //  ROM.    std::cout << test.Get() << std::endl ; return 0; } 



7.
 class Test { private: int i; public: constexpr Test(int value): i(value) {} ; int Get() // Get  ,  ,       (i),     .   ,       . { return i + 1; } } ; const Test test(10); int main() { std::cout << test.Get() << std::endl ; return 0; } 



8. ROM,
 class ITest { private: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class Test: public ITest { private: int i; public: constexpr Test(int value): i(value), ITest(value+1) {} ; int Get() const override { return i + 1; } } ; const Test test(10); //  ROM. i     constexpr , j   constexpr  ITest int main() { std::cout << test.Give() << std::endl ; return 0; } 



9. ROM , RAM
 class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) { k = value; j = value + 10; } } ; TestImpl testImpl(1); //    RAM. class Test: public ITest { private: int i; TestImpl & obj; //    public: constexpr Test(int value, TestImpl & ref): i(value), obj(ref), ITest(value+1) { } ; int Get() const override { return i + 1; } bool Set() const { obj.Set(100) ; //     return true; } } ; constexpr Test test(10, testImpl); //  ROM.     constexpr  int main() { std::cout << test.Set() << std::endl ; return 0; } 



10. ROM
 class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) { k = value; j = value + 10; } } ; class Test: public ITest { private: int i; TestImpl & obj; //    public: constexpr Test(int value, TestImpl & ref): i(value), obj(ref),ITest(value+1) { } ; int Get() const override { return i + 1; } bool Set() const { obj.Set(100) ; //     return true; } } ; int main() { static TestImpl testImpl(1); //  static constexpr Test test(10, testImpl); //    ROM.     constexpr  std::cout << test.Set() << std::endl ; return 0; } 



11. RAM
 class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) { k = value; j = value + 10; } } ; class Test: public ITest { private: int i; TestImpl & obj; //    public: constexpr Test(int value, TestImpl & ref): i(value), obj(ref),ITest(value+1) { } ; int Get() const override { return i + 1; } bool Set() const { obj.Set(100) ; //     return true; } } ; int main() { static TestImpl testImpl(1); //  const Test test(10, testImpl); //    RAM. std::cout << test.Set() << std::endl ; return 0; } 



12. .
 class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) { k = value; j = value + 10; } } ; class Test: public ITest { private: int i; TestImpl obj; //   TestImpl public: constexpr Test(int value): i(value), obj(TestImpl(value)), //   constexpr   TestImpl ITest(value+1) { } ; int Get() const { return i + 1; } bool Set() const { obj.Set(100) ; //     return true; } } ; int main() { static TestImpl testImpl(1); //  static const Test test(10); //   std::cout << test.Set() << std::endl ; return 0; } 



13.
 class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: constexpr TestImpl(int value): k(value), ITest(value) //   constexpr { } int Get() const override { return j + 10; } void Set(int value) //   ,  k  j.       ROM,        RAM { k = value; j = value + 10; } } ; class Test: public ITest { private: int i; TestImpl obj; public: constexpr Test(int value): i(value), obj(TestImpl(value)), // constexpr     obj,    obj  .rodata . ITest(value+1) { } ; int Get() const { return i + 1; } bool Set() const { obj.Set(100) ; //        constexpr     return true; } } ; int main() { static TestImpl testImpl(1); //  static const Test test(10); //   std::cout << test.Set() << std::endl ; return 0; } 



14. ROM
 class ITest { protected: int j; public: virtual int Get() const = 0; constexpr ITest(int value) : j(value) { } int Give() const { return j ; } }; class TestImpl: public ITest { private: int k; public: constexpr TestImpl(int value): k(value), ITest(value) { } int Get() const override { return j + 10; } void Set(int value) const //   const { //do something } } ; class Test: public ITest { private: int i; const TestImpl obj; // ,    constexpr  public: constexpr Test(int value): i(value), obj(TestImpl(value)), ITest(value+1) { } ; int Get() const { return i + 1; } bool Set() const { obj.Set(100) ; //   return true; } } ; int main() { //static TestImpl testImpl(1); //  static const Test test(10); //    ROM.     constexpr  std::cout << test.Set() << std::endl ; return 0; } 



, , , constexpr .
 class Test { private: int k[100]; constexpr void InitArray() { int i = 0; for(auto& it: k) { it = i++ ; } } public: constexpr Test(): k() { InitArray(); // constexpr     } int Get(int index) const { return k[index]; } } ; int main() { static const Test test; //    ROM.     ,  constexpr . std::cout << test.Get(10) << std::endl ; return 0; } 


المراجع:
IAR C/C++ Development Guide
Constexpr constructors (C++11)
constexpr (C++)

PS.
Valdaros . ++ N1076.pdf

1. ( mutable ) Undefined Behaviour. أي

  const int ci = 1 ; int* iptr = const_cast<int*>(&ci); //UB,       *iptr = 2 ; 

  int i = 1; const int* ci = &i ; int* iptr = const_cast<int *> (ci); //   *iptr = 2 ; // UB,   i 

2. , , . , :

 class Test { public: int i; constexpr Test(): i(0) { foo(this) ; } } ; Test *test1; constexpr void foo(Test* value) { value->i = 1; //       0  1 test1 = value ; //           } const Test test; int main() { test1->i = 2; //          2. std::cout << &test << std::endl; } 

. , constexpr , constexpr . RAM.

, const — constexpr, , , - , .

 class Test { public: int i; constexpr Test(): i(0) { foo(this) ; } } ; Test *test1; constexpr void foo(Test* value) { value->i = 1; //       0  1 test1 = value ; //           } constexpr Test test; // / Error[Pe2400]: calling the default constructor for "Test" does not produce a constant value main.cpp 151 int main() { test1->i = 2; //          2. std::cout << &test << std::endl; } 

Source: https://habr.com/ru/post/ar453262/


All Articles