خمس سنوات من استخدام C ++ لمشاريع ميكروكنترولر في الإنتاج

سوف أخبرك في هذه المقالة كيف قمت بنقل الشركات التي عملت بها لأكثر من خمس سنوات من إدارة المشروعات الخاصة بالميكروكنترولر في C إلى C ++ وماذا جاء منها (المفسد: كل شيء سيء).

قليلا عن نفسك


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

السنة الاولى


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

typedef const struct _uart_init { USART_TypeDef *USARTx; uint32_t baudrate; ... } uart_cfg_t; int uart_init (uart_cfg_t *cfg); int uart_start_tx (int fd, void *d, uint16_t l); int uart_tx (int fd, void *d, uint16_t l, uint32_t timeout); 

كان لهذا النهج المزايا التالية:

  1. استمر الرمز ليكون كود C. المزايا التالية تتبع ذلك:
    • من الأسهل التحكم في "الكائنات" ، لأنه من السهل تتبع من وأين يتسبب ذلك وفي أي تسلسل (باستثناء الانقطاعات ، ولكن ليس في هذه المقالة) ؛
    • لتخزين "المؤشر إلى الكائن" يكفي تذكر fd الذي تم إرجاعه ؛
    • إذا تم حذف "الكائن" ، فعندما تحاول استخدامه ، ستتلقى خطأً متطابقًا في القيمة المرجعة للدالة ؛
  2. أتاح تجريد مثل هذه الكائنات على HAL المستخدم هناك إمكانية كتابة كائنات قابلة للتخصيص للمهمة من بنية التهيئة الخاصة بها (وفي حالة نقص وظائف HAL ​​، يمكن للمرء إخفاء الوصول إلى السجلات داخل "الكائنات").

سلبيات:

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

عندما سئلوا عن سبب عدم اختيار C ++ عند إنشاء البنية التحتية بأكملها ، أجابوا بشيء من هذا القبيل: "حسنًا ، يؤدي C ++ إلى تكاليف إضافية قوية ، وتكاليف ذاكرة غير منضبطة ، بالإضافة إلى ملف برنامج تشغيل كبير قابل للتنفيذ." ربما كانوا على حق. في الواقع ، في وقت بداية التصميم ، لم يكن هناك سوى 3.0.5 دول مجلس التعاون الخليجي ، والتي لم تتألق مع الود الخاص ل C ++ (لا يزال يتعين علينا العمل معها لكتابة البرامج تحت QNX6). لم يكن هناك constexpr و C ++ 11/14 ، مما يسمح لك بإنشاء كائنات عمومية ، والتي كانت في جوهرها بيانات في منطقة .data ، محسوبة في مرحلة الترجمة وطرقها.

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

تحقيق كل هذا وإدراك أن C ++ الآن ليس هو نفسه كما كان في GCC 3.0.5 ، بدأت في إعادة كتابة الجزء الرئيسي من الوظيفة في C ++. للبدء ، قم بالعمل مع الأجهزة الطرفية للأجهزة الدقيقة ، ثم الأجهزة الطرفية للأجهزة الخارجية. في الواقع ، كانت هذه مجرد قذيفة أكثر ملاءمة لما كان متاحًا في ذلك الوقت.

السنة الثانية والثالثة


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

السنة الرابعة والخامسة


  • جميع الكائنات عالمية وتتضمن روابط لبعضها البعض في مرحلة التجميع (وفقًا لهندسة المشروع) ؛
  • يتم تخصيص الذاكرة لجميع الكائنات في مرحلة التصميم ؛
  • بواسطة كائن فئة لكل دبوس.
  • كائن يقوم بتغليف كل المسامير لتهيئتها باستخدام طريقة واحدة ؛
  • كائن تحكم RCC يقوم بتغليف كل الكائنات الموجودة في ناقل الأجهزة ؛
  • CAN <-> مشروع محول RS485 وفقا لبروتوكول العميل يحتوي على 60 كائن ؛
  • في حالة وجود شيء ما على هذا المستوى على مستوى طبقة تجريد الأجهزة (HAL) أو مستوى فئة كائن ما ، لا يتعين عليك فقط "إصلاح المشكلة" ، ولكن أيضًا التفكير في كيفية إصلاحها حتى يعمل هذا الإصلاح على كافة التكوينات الممكنة لهذه الوحدة النمطية .
  • لا يمكن حساب القوالب و constexpr المستخدمة قبل عرض الخريطة ، ملفات asm و bin للبرنامج الثابت النهائي (أو بدء تصحيح الأخطاء في متحكم دقيق) ؛
  • في حالة وجود خطأ في القالب ، يتم إخراج رسالة بطول ثلث تكوين المشروع من GCC. قراءة وفهم شيء منه هو إنجاز منفصل.

النتائج


الآن أفهم ما يلي:
  1. استخدام "منشئو الوحدة النمطية العالمي" فقط يعقد البرنامج دون داع. من الأسهل بكثير ضبط سجلات التكوين لمشروع جديد بدلاً من الخوض في العلاقات بين الكائنات ، ثم أيضًا في مكتبة HAL ؛
  2. لا تخف من استخدام C ++ خوفًا من أن "تلتهم الكثير من الذاكرة" أو "تكون أقل من C". لا ، ليس كذلك. يجب أن تكون خائفًا من أن استخدام الكائنات والعديد من طبقات التجريد سيجعل الكود غير قابل للقراءة ، وتصحيح الأخطاء سيكون إنجازًا بطوليًا ؛
  3. إذا كنت لا تستخدم أي شيء "معقد" ، مثل القوالب والميراث وغيرها من السحر الجذاب لـ C ++ ، فلماذا تستخدم C ++ على الإطلاق؟ فقط من أجل الأشياء؟ هل يستحق كل هذا العناء؟ من أجل كائنات عالمية ثابتة دون استخدام / حذف جديد محظور على بعض المشاريع؟

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

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


All Articles