في بداية عام 2018 ، ظهرت سلسلة من المقالات على مدونتنا مخصصة للتحقق السادس من الكود المصدري لمشروع Chromium. تتضمن الدورة 8 مقالات مخصصة للأخطاء وتوصيات لمنعها. تسبب مقالتان في نقاش ساخن ، وحتى الآن نادراً ما تلقيت تعليقات على الموضوعات التي تم تناولها فيها. ربما ، ينبغي تقديم بعض التفسيرات الإضافية ، وكما يقولون ، حدد النقطة i.
لقد مر عام منذ كتابة سلسلة من المقالات المكرسة للتحقق التالي من أكواد مصدر مشروع Chromium:
- Chromium: فحص المشروع السادس و 250 خطأ
- الكروم الجميلة و memset الخرقاء
- كسر والانهيار
- الكروم: تسرب الذاكرة
- الأخطاء المطبعية في الكروم
- Chromium: استخدام بيانات غير دقيقة
- لماذا من المهم التحقق من وظيفة malloc التي يتم إرجاعها
- الكروم: أخطاء أخرى
تسببت المقالات حول
memset و
malloc في المناقشات التي لا تزال غريبة بالنسبة لي. على ما يبدو ، كان هناك بعض سوء الفهم بسبب حقيقة أنني لم أضع أفكاري بوضوح. قررت العودة إلى هذه المقالات وتقديم بعض التوضيحات.
memset
لنبدأ بمقال عن
memset ، لأن كل شيء بسيط هنا. كان هناك جدل حول أفضل طريقة لتهيئة الهياكل. كتب الكثير من المبرمجين أنه من الأفضل تقديم توصية بعدم الكتابة:
HDHITTESTINFO hhti = {};
و هكذا:
HDHITTESTINFO hhti = { 0 };
الحجج:
- من السهل ملاحظة إنشاء {0} عند قراءة الكود من {}.
- بناء {0} أكثر سهولة من {}. وهذا يعني ، 0 يشير إلى أن الهيكل مليء بالأصفار.
وفقًا لذلك ، عُرض عليّ تغيير مثال التهيئة هذا في المقالة. لا أوافق على الحجج ولا أخطط لتعديل المقال. الآن أبرر موقفي.
حول الرؤية. أعتقد أن هذه مسألة ذوق وعادات. لا أعتقد أن وجود 0 داخل أقواس مجعد يغير الوضع بشكل جذري.
ولكن مع الحجة الثانية أنا لا أتفق على الإطلاق. يعطي السجل مثل {0} سببًا لسوء فهم الكود. على سبيل المثال ، يمكنك حساب أنه في حالة استبدال الرقم "0" بالرقم "1" ، سيتم تهيئة جميع الحقول بوحدات. لذلك ، هذا النمط من الكتابة ضار أكثر من كونه مفيدًا.
لدى محلل PVS-Studio تشخيص
V1009 ذي صلة بهذا الموضوع ، وهو الوصف الذي
سأقدمه الآن.
V1009. تحقق من تهيئة الصفيف. تتم تهيئة العنصر الأول فقط بشكل صريح.اكتشف المحلل وجود خطأ محتمل بسبب حقيقة أنه عند الإعلان عن صفيف ، يتم تحديد القيمة لعنصر واحد فقط. وبالتالي ، سيتم تهيئة بقية العناصر ضمنيًا إلى الصفر أو المُنشئ الافتراضي.
النظر في مثال من التعليمات البرمجية المشبوهة:
int arr[3] = {1};
ربما كان المبرمج يتوقع أن يتكون
arr من وحدة واحدة ، لكن هذا ليس كذلك. سيتألف الصفيف من القيم 1 و 0 و 0.
الكود الصحيح هو:
int arr[3] = {1, 1, 1};
يمكن أن يحدث هذا التشويش بسبب التشابه مع بنية
arr = {0} ، التي تهيئ الصفيف بأكمله بالأصفار.
إذا تم استخدام تصاميم مماثلة بنشاط في مشروعك ، يمكنك تعطيل هذه التشخيصات.
كما لا ينصح بإهمال رؤية الكود.
على سبيل المثال ، يتم كتابة التعليمات البرمجية لترميز قيم اللون كما يلي:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00 }; int Green[3] = { 0x00, 0xff };
بفضل التهيئة الضمنية ، يتم ضبط جميع الألوان بشكل صحيح ، لكن من الأفضل إعادة كتابة الكود بطريقة أوضح:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00, 0x00, 0x00 }; int Green[3] = { 0x00, 0xff, 0x00 };
malloc
قبل قراءة المزيد ، أطلب منك تحديث محتويات المقالة "
لماذا من المهم التحقق من وظيفة malloc التي تم إرجاعها ". لقد ولدت هذه المقالة الكثير من النقاش والنقد. فيما يلي بعض المناقشات:
reddit.com/r/cpp ،
reddit.com/r/C_Programming ،
habr.com . في بعض الأحيان يكتبون لي عن هذا المقال في البريد الآن.
ينتقد القراء المقال على النقاط التالية:
1. إذا عادت malloc NULL ، فمن الأفضل الخروج فورًا من البرنامج بدلاً من كتابة مجموعة من ifs ومحاولة معالجة نقص الذاكرة بطريقة أو بأخرى ، لأنه من المستحيل في كثير من الأحيان تنفيذ البرنامج على أي حال.لم أطلب على الإطلاق حتى آخر التعامل مع عواقب نفاد الذاكرة ، ورمي الخطأ أعلى وأعلى. إذا كان مسموحًا لإغلاق التطبيق دون سابق إنذار ، فليكن ذلك. للقيام بذلك ، هناك حاجة إلى فحص واحد فقط بعد
malloc أو استخدام
xmalloc (راجع الفقرة التالية).
اعترضت وحذرت من عدم وجود عمليات تدقيق ، بسبب استمرار البرنامج في العمل "كما لو لم يحدث شيء". هذا مختلف تماما. هذا أمر خطير لأنه يؤدي إلى سلوك غير محدد ، تلف البيانات ، وما إلى ذلك.
2. لم يتم التحدث عن الحل ، والذي يتكون في كتابة مغلف الوظائف لتخصيص الذاكرة مع التحقق اللاحق أو استخدام الوظائف الحالية ، مثل xmalloc .أوافق ، فاتني هذه اللحظة. عند كتابة مقال ، أنا فقط لم أفكر في كيفية إصلاح الوضع. كان أكثر أهمية بالنسبة لي أن أنقل للقارئ ماهية خطر عدم التحقق. كيفية إصلاح خطأ هي بالفعل مسألة ذوق وتفاصيل التنفيذ.
وظيفة
xmalloc ليست جزءًا من مكتبة C القياسية (راجع "
ما هو الفرق بين xmalloc و malloc؟ "). ومع ذلك ، يمكن الإعلان عن هذه الوظيفة في مكتبات أخرى ، على سبيل المثال ، في مكتبة أدوات
جنو (مكتبة
جنو ).
جوهر الوظيفة هو أن البرنامج ينتهي إذا فشل تخصيص الذاكرة. قد يبدو تنفيذ هذه الوظيفة كما يلي:
void* xmalloc(size_t s) { void* p = malloc(s); if (!p) { fprintf (stderr, "fatal: out of memory (xmalloc(%zu)).\n", s); exit(EXIT_FAILURE); } return p; }
وفقًا لذلك ، من خلال استدعاء وظيفة
xmalloc في كل مكان بدلاً من
malloc ، يمكنك التأكد من أن البرنامج لن يكون لديه سلوك غير محدد بسبب أي استخدام للمؤشر الفارغ.
لسوء الحظ ،
xmalloc هو أيضا ليس علاجا لجميع العلل. يجب أن نتذكر أن استخدام
xmalloc أمر غير مقبول عندما يتعلق الأمر بكتابة رمز المكتبة. سأتحدث عن هذا بعد قليل.
3. كانت معظم التعليقات كما يلي: "في الممارسة العملية ، malloc لا يُرجع NULL مطلقًا ."لحسن الحظ ، أنا لا أفهم وحدك أن هذا هو النهج الخاطئ. أعجبتني حقًا هذا
التعليق في دعمي:
من تجربة مناقشة مثل هذا الموضوع ، يشعر المرء بوجود طائفتين على الإنترنت. أتباع الأول مقتنعون تمامًا بأنه في نظام Linux ، لا يُرجع malloc أبدًا NULL. أتباع الثانية مقتنعون تمامًا بأنه إذا تعذر تخصيص الذاكرة الموجودة في البرنامج ، ولكن لا يمكن القيام بأي شيء من حيث المبدأ ، فأنت تحتاج فقط إلى السقوط. لا يمكنك إقناعهم بأي طريقة. خاصة عندما تتقاطع هاتان الطائفتان. يمكنك أن تأخذ فقط أمرا مفروغا منه. ولا يهم مورد ملف التعريف الذي تدور حوله المناقشة.فكرت وقررت اتباع النصيحة ولن أحاول إقناع :). دعونا نأمل أن تكتب فرق التطوير هذه البرامج غير الضرورية فقط. على سبيل المثال ، إذا تدهورت بعض البيانات في اللعبة أو تعطلت اللعبة ، فهذه ليست مشكلة كبيرة.
الشيء الوحيد المهم هو أن مطوري المكتبات وقواعد البيانات وما إلى ذلك لا يقومون بذلك.
دعوة إلى المكتبة ومطوري الأكواد المسؤولين
إذا كنت تقوم بتطوير مكتبة أو رمز مسؤول آخر ، فقم دائمًا بالتحقق من قيمة المؤشر الذي تم إرجاعه بواسطة الدالة
malloc / realloc ، وأعد رمز الخطأ إلى الخارج إذا تعذر تخصيص الذاكرة.
في المكتبات ، لا يمكنك استدعاء وظيفة
الخروج إذا كنت غير قادر على تخصيص الذاكرة. لنفس السبب ، لا يمكنك استخدام
xmalloc . بالنسبة للعديد من التطبيقات ، من غير المقبول أخذها وتعطلها. وبسبب هذا ، على سبيل المثال ، قد تكون قاعدة البيانات تالفة. قد يتم فقد البيانات التي تم حسابها لعدة ساعات. لهذا السبب ، قد يكون البرنامج عرضة لرفض الثغرات الأمنية في الخدمة ، عندما ينتهي التطبيق متعدد مؤشرات الترابط ، بدلاً من معالجة الحمل المتزايد بشكل صحيح.
لا يمكن افتراض كيف وكيف سيتم استخدام المكتبة. لذلك ، ينبغي افتراض أن التطبيق يمكنه حل المهام المهمة جدًا. وفقط "قتله" عن طريق استدعاء
الخروج ليس جيدًا. على الأرجح ، يتم كتابة مثل هذا البرنامج مع الأخذ في الاعتبار إمكانية نفاد الذاكرة ويمكن القيام بشيء في هذه الحالة. على سبيل المثال ، لا يمكن لنظام CAD ، بسبب التفتت القوي للذاكرة ، تخصيص مخزن مؤقت للذاكرة كافٍ للعملية التالية. ولكن هذا ليس سببًا لها كي تنتهي في وضع الطوارئ بفقدان البيانات. يمكن للبرنامج أن يتيح حفظ مشروع وإعادة تشغيل نفسه في الوضع العادي.
لا يمكنك بأي حال من الأحوال الاعتماد على حقيقة أن
malloc يمكنه دائمًا تخصيص الذاكرة. من غير المعروف على أي نظام أساسي وكيف سيتم استخدام المكتبة. إذا كان نقص الذاكرة أمرًا غريبًا على أحد الأنظمة ، فيمكن أن يكون وضعًا شائعًا للغاية على نظام آخر.
لا يمكن أن نأمل أنه إذا عادت
malloc NULL ،
فسيتعطل البرنامج. أي شيء يمكن أن يحدث. كما وصفت في
المقالة ، يمكن للبرنامج كتابة البيانات على عنوان غير صفري على الإطلاق. نتيجة لذلك ، قد تكون بعض البيانات تالفة ، مما يؤدي إلى عواقب لا يمكن التنبؤ بها. حتى
memset أمر خطير. إذا تم ملء البيانات بترتيب عكسي ، فسوف تتلف بعض البيانات في البداية ، وعندها فقط سيتعطل البرنامج. لكن السقوط قد يحدث بعد فوات الأوان. إذا تم استخدام البيانات التالفة في مؤشرات الترابط المتوازية في وقت وظيفة
memset ، فإن العواقب قد تكون قاتلة. يمكنك الحصول على معاملة تالفة في قاعدة البيانات أو إرسال أمر لحذف الملفات "غير الضرورية". أي شيء يمكن أن يحدث في الوقت المناسب. أقترح على القارئ أن يتخيل بشكل مستقل ما يمكن أن يؤدي إليه استخدام البيانات المهملة في الذاكرة.
وبالتالي ، لدى المكتبة خيار صحيح واحد فقط للعمل مع وظائف
malloc . تحتاج إلى التحقق فورًا من أن الوظيفة قد تم إرجاعها ، وإذا كانت خالية ، فقم بإرجاع حالة الخطأ.
روابط أقسام الموقع
- معالجة OOM .
- المرح مع مؤشرات NULL: الجزء 1 ، الجزء 2 .
- ما يجب أن يعرفه كل مبرمج C حول السلوك غير المحدد: الجزء 1 ، الجزء 2 ، الجزء 3 .

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