في بداية عام 2018 ، تم استكمال مدونتنا بسلسلة من المقالات حول الفحص السادس للشفرة المصدرية لمشروع Chromium. تتضمن السلسلة 8 مقالات عن الأخطاء والتوصيات المتعلقة بمنعها. أثارت مقالتان نقاشًا ساخنًا ، وما زلت أحصل أحيانًا على تعليقات عبر البريد الإلكتروني حول مواضيع تتناولها. ربما ، يجب أن أقدم توضيحات إضافية وكما يقولون ، اجعل الأمور في نصابها.
لقد مر عام على كتابة سلسلة من المقالات حول فحص منتظم لرمز مصدر مشروع Chromium:
- Chromium: الفحص السادس للمشروع و 250 خطأ
- كروم لطيف و Memset الخرقاء
- كسر والانهيار
- الكروم: تسرب الذاكرة
- الكروم: الأخطاء المطبعية
- Chromium: استخدام البيانات غير الموثوق بها
- لماذا من المهم التحقق من وظيفة malloc التي تم إرجاعها
- الكروم: أخطاء أخرى
المقالات المكرّسة ل
memset و
malloc تسببت في النقاشات
وتواصلها ، الأمر الذي
أثار دهشتي . على ما يبدو ، كان هناك بعض الالتباس بسبب حقيقة أنني لم أكن دقيقة بشكل دقيق عند التعبير عن أفكاري. قررت العودة إلى هذه المقالات وتقديم بعض التوضيحات.
memset
لنبدأ بمقال حول
memset ، لأن كل شيء هنا بسيط. ظهرت بعض الحجج حول أفضل طريقة لتهيئة الهياكل. كتب العديد من المبرمجين أنه من الأفضل إعطاء التوصية بعدم الكتابة:
HDHITTESTINFO hhti = {};
ولكن الكتابة بالطريقة التالية:
HDHITTESTINFO hhti = { 0 };
الأسباب:
- الإنشاء {0} يكون أسهل عند ملاحظة الكود ، من {}.
- البناء {0} أكثر قابلية للفهم ، {}. مما يعني ، 0 تشير إلى أن الهيكل مليء بالأصفار.
وفقًا لذلك ، اقترح علي القراء تغيير مثال التهيئة هذا في المقالة. لا أوافق على الحجج ولا أخطط لإجراء أي تعديلات في المقالة. سأقوم الآن بشرح رأيي وتقديم بعض الأسباب.
بالنسبة للرؤية ، أعتقد ، إنها مسألة ذوق وعادات. لا أعتقد أن وجود 0 داخل الأقواس يغير الوضع بشكل جذري.
أما بالنسبة للحجة الثانية ، فأنا لا أتفق معها تمامًا. يعطي سجل النوع {0} سببًا لإدراك الكود بطريقة غير صحيحة. على سبيل المثال ، يمكنك افتراض أنك إذا استبدلت بالرقم 0 ، فسيتم تهيئة جميع الحقول بحقول. لذلك ، من المرجح أن يكون أسلوب الكتابة هذا ضارًا وليس مفيدًا.
يحتوي محلل PVS-Studio على
V1009 تشخيصي
مرتبط ،
ويرد وصفه أدناه.
V1009. تحقق من تهيئة الصفيف. تتم تهيئة العنصر الأول فقط بشكل صريح.اكتشف المحلل وجود خطأ محتمل يتعلق بحقيقة أنه عند الإعلان عن صفيف ، يتم تحديد القيمة فقط لعنصر واحد. وبالتالي ، سيتم تهيئة العناصر المتبقية ضمنيًا بصفر أو بواسطة مُنشئ افتراضي.
لننظر في مثال الكود المشبوه:
int arr[3] = {1};
ربما يتألف المبرمج المتوقع من
arr بالكامل من الآخرين ، لكنه ليس كذلك. سيتألف الصفيف من القيم 1 و 0 و 0.
كود صحيح:
int arr[3] = {1, 1, 1};
قد يحدث هذا التشويش بسبب التشابه مع
arr construction
= {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 (en). في بعض الأحيان لا يزال القراء يرسلون لي بريداً إلكترونياً حول هذا المقال
ينتقد القراء المقال بسبب النقاط التالية:
1. إذا عادت malloc NULL ، فمن الأفضل إنهاء البرنامج على الفور ، بدلاً من كتابة مجموعة من if -s ومحاولة معالجة الذاكرة بطريقة أو بأخرى ، بسبب تنفيذ البرنامج في كثير من الأحيان يكون مستحيلًا على أي حال.لم أمارس القتال حتى النهاية مع عواقب تسرب الذاكرة ، وذلك بتمرير الخطأ أعلى وأعلى. إذا كان مسموحًا للتطبيق بإنهاء عمله دون سابق إنذار ، فليكن كذلك. لهذا الغرض ، يكفي إجراء فحص واحد بعد
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 مطلقًا ."لحسن الحظ ، أنا لست الوحيد الذي يفهم أن هذا هو النهج الخاطئ. أعجبتني حقًا هذا
التعليق في دعمي:
وفقًا لتجربتي في مناقشة هذا الموضوع ، لدي شعور بأن هناك طائفتين في الإنترنت. يؤمن أتباع الأول بقوة أن malloc لا يُرجع أبدًا NULL ضمن Linux. يدعي مؤيدو الثانية بكل إخلاص أنه إذا تعذر تخصيص الذاكرة في البرنامج ، فلا يمكن القيام بأي شيء ، يمكنك التعطل فقط. لا توجد وسيلة للإفراط في إقناعهم. خاصة عندما تتقاطع هاتان الطائفتان. يمكنك أن تأخذ فقط على النحو الوارد. وحتى أنه ليس من المهم على أي مورد متخصص تجري المناقشة.فكرت لفترة من الوقت وقررت اتباع النصيحة ، لذلك لن أحاول إقناع أي شخص :). نأمل أن تكتب مجموعات المطورين هذه البرامج غير المميتة فقط. على سبيل المثال ، في حالة تلف بعض البيانات الموجودة في اللعبة ، فلا يوجد شيء مهم فيها.
الشيء الوحيد المهم هو أن مطوري المكتبات وقواعد البيانات يجب ألا يفعلوا مثل هذا.
مناشدة مطوري الكود والمكتبات التي تعتمد بشكل كبير
إذا كنت تقوم بتطوير مكتبة أو رمز آخر يعتمد بشدة ، فقم دائمًا بالتحقق من قيمة المؤشر الذي تم إرجاعه بواسطة دالة
malloc / realloc وإرجاع رمز خطأ للخارج إذا تعذر تخصيص الذاكرة.
في المكتبات ، لا يمكنك استدعاء وظيفة
الخروج ، إذا فشل تخصيص الذاكرة. لنفس السبب ، لا يمكنك استخدام
xmalloc . بالنسبة للعديد من التطبيقات ، من غير المقبول ببساطة إجهاضها. وبسبب هذا ، على سبيل المثال ، يمكن تلف قاعدة البيانات. يمكن للمرء أن يفقد البيانات التي تم تقييمها لعدة ساعات. لهذا السبب ، يمكن تحقيق البرنامج في الثغرات الأمنية "رفض الخدمة" ، عند إنهاء تطبيق متعدد مؤشرات الترابط بدلاً من المعالجة الصحيحة لعبء العمل المتزايد.
لا يمكن افتراض ذلك ، وبأي طرق وفي أي مشاريع سيتم استخدام المكتبة. لذلك ، ينبغي افتراض أن التطبيق قد يحل المهام الحرجة للغاية. هذا هو السبب في قتل فقط عن طريق استدعاء
الخروج ليست جيدة. على الأرجح ، يتم كتابة مثل هذا البرنامج مع الأخذ في الاعتبار احتمال نقص الذاكرة ويمكن أن تفعل شيئا في هذه الحالة. على سبيل المثال ، لا يمكن لنظام CAD تخصيص مخزن مؤقت مناسب للذاكرة سيكون كافياً للتشغيل المنتظم بسبب التجزئة القوية للذاكرة. في هذه الحالة ، ليس السبب في سحقها في وضع الطوارئ مع فقد البيانات. يمكن للبرنامج أن يوفر فرصة لحفظ المشروع وإعادة تشغيل نفسه بشكل طبيعي.
في أي حال من الأحوال لا يمكن الاعتماد على
malloc أنه سيكون دائمًا قادرًا على تخصيص الذاكرة. من غير المعروف على أي نظام أساسي وكيف سيتم استخدام المكتبة. إذا كانت حالة انخفاض الذاكرة على أحد الأنظمة الأساسية غريبة ، فقد يكون ذلك موقفًا شائعًا على الآخر.
لا يمكننا توقع أنه في حالة إرجاع
malloc NULL ، فسوف يتعطل البرنامج. أي شيء يمكن أن يحدث. كما هو موضح في
المقالة ، قد يكتب البرنامج البيانات وليس العنوان الخالي. نتيجة لذلك ، قد تكون بعض البيانات تالفة ، مما يؤدي إلى عواقب لا يمكن التنبؤ بها. حتى
memset أمر خطير. في حالة انتقال الحشو بالبيانات بترتيب عكسي ، فسوف تتلف بعض البيانات أولاً ، ثم سيتعطل البرنامج. لكن الانهيار قد يحدث بعد فوات الأوان. إذا تم استخدام البيانات الملوثة في خيوط متوازية أثناء
عمل وظيفة
memset ، فقد تكون العواقب مميتة. يمكنك الحصول على معاملة تالفة في قاعدة بيانات أو إرسال أوامر لإزالة الملفات "غير الضرورية". أي شيء لديه فرصة ليحدث. أقترح على القارئ أن يحلم بنفسك ، ما قد يحدث بسبب استخدام القمامة في الذاكرة.
وبالتالي ، فإن المكتبة لديها طريقة واحدة صحيحة فقط للعمل مع وظائف
malloc . تحتاج إلى التحقق فورًا من أن الوظيفة قد تم إرجاعها ، وإذا كانت خالية ، فقم بإرجاع حالة خطأ.
روابط إضافية
- معالجة OOM
- المرح مع مؤشرات NULL: الجزء 1 ، الجزء 2
- ما يجب أن يعرفه كل مبرمج C حول السلوك غير المحدد: الجزء 1 ، الجزء 2 ، الجزء 3