محتويات الدورة

C / المتقدم: حدود اللغة والأنظمة / الدرس 13

السلوك غير المعرّف والمحاذاة وقواعد الوصول

صنف ضمانات اللغة بدقة واستبدل حيل التمثيل بعمليات يمكن تبرير عقودها.

وقت القراءة التقريبي: 5 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • تمييز السلوك غير المعرّف وغير المحدد والمعرّف من التنفيذ.
  • مراجعة المحاذاة والنوع الفعلي والترتيب.
  • استخدام الوصول البايتي وmemcpy لعمليات تمثيل مبررة.

حريات مختلفة تتطلب استجابات مختلفة

لا يفرض السلوك غير المعرّف متطلبات على تنفيذ يبلغه؛ فلا يصح جعل الانهيار أو الإجابة المعقولة أو النتيجة المتغيرة بالتحسين مخرجا متوقعا له. يختار السلوك غير المحدد بين بدائل مسموحة دون إلزام بالتوثيق. أما السلوك المعرّف من التنفيذ فيلزم التنفيذ بتوثيق اختياره. ليست هذه أوصافا متبادلة لعبارة «الأمر يعتمد».

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

التحويل لا ينشئ المحاذاة

لكل نوع كائن كامل متطلب محاذاة. تستعلم _Alignof عنه، وتطلب _Alignas محاذاة مدعومة لكائن. لا يضمن تحويل مؤشر بايت عشوائي إلى int * ملاءمة العنوان للمحاذاة ولا كفاية التخزين ولا صلاحية الوصول بنوع int وحده.

يطلب المثال محاذاة تناسب max_align_t ولا يفحص إلا علاقات، فلا يدعي عددا عالميا للبايتات. توفر malloc محاذاة تناسب الأنواع ذات المحاذاة الأساسية؛ وقد تحتاج الأنواع الممتدة الأشد محاذاة إلى مرفق تخصيص محاذى مدعوم. عند استخدام aligned_alloc التزم بقيود المحاذاة المدعومة ومضاعفات الحجم وافحص الفشل.

c طلب المحاذاة دون اختراع عرض
#include <stddef.h>
#include <stdio.h>

int main(void)
{
    _Alignas(max_align_t) unsigned char storage[32];
    storage[0] = 1;
    if (_Alignof(max_align_t) < _Alignof(int)) return 1;
    puts(storage[0] == 1 ? "alignment contract checked" : "unexpected");
    return 0;
}
الناتج المتوقع
alignment contract checked

للكائن المحاذاة المدعومة المطلوبة؛ ولا يحوله البرنامج إلى نوع كائن غير مرتبط.

توافق التخزين ونوع الوصول

تقيد C17 أنواع القيم اليسرى التي يجوز بها الوصول إلى قيمة كائن مخزنة. للأنواع المتوافقة والأنواع المقابلة بإشارة أو دونها والمجاميع ذات الصلة والأنواع المحرفية سماحات محددة؛ ولا يتجاوزها تحويل مؤشر عشوائي. وللتخزين المخصص قواعد نوع فعلي إضافية مرتبطة بالكتابة والنسخ. لا تختزل القاعدة المعقدة بقول «كل المؤشرات متبادلة».

يستطيع الوصول المحرفي فحص تمثيل كائن. ينسخ البرنامج التالي تمثيل int إلى تخزين unsigned char ثم يعيده إلى int آخر من النوع نفسه. لا يخترع ترتيب بايتات محمولا ولا يعيد تفسير بايتات عشوائية كعائم صالح. تعبر memcpy عن نسخ بايتي دون فك إشارة نوعية سيئة المحاذاة.

c إعادة نسخ تمثيل إلى نوعه الأصلي
#include <stdio.h>
#include <string.h>

int main(void)
{
    int source = 123;
    int restored = 0;
    unsigned char representation[sizeof source];
    memcpy(representation, &source, sizeof source);
    memcpy(&restored, representation, sizeof restored);
    printf("restored=%d\n", restored);
    return 0;
}
الناتج المتوقع
restored=123

جاءت البايتات من int صالح وأُعيدت إلى int آخر في التنفيذ نفسه.

يفترض التحسين عمليات صالحة

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

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

مراجعة الالتزامات قبل التجارب

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

استخدم التحذيرات والتحليل الساكن وكواشف مختارة لجمع أدلة إضافية. لا تغطي UBSan كل عملية غير معرّفة، ويصعب كشف بعض عيوب النوع الفعلي ديناميكيا. احفظ اختبارات مستقلة للسلوك المعرّف بعد الإصلاح؛ ولا تخزن مخرج برنامج غير معرّف مرجعا لاختبار التراجع.

الأخطاء الشائعة وإصلاحها

يعمل تحويل مخزن بايتات على معمارية واحدة فقط.

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

يبدو أن تعطيل التحسين يصلح النتيجة.

لماذا يحدث؟
غير السلوك غير المعرّف أعراضه.
كيف تصلحه؟
ابحث عن العملية غير الصالحة وأزلها.
كيف تتجنب تكراره؟
شغل حالات تراجع معرّفة بمستويات تحسين متعددة.

يقبل الكاشف البرنامج فيُعد التحويل آمنا.

لماذا يحدث؟
اعتُبرت تغطية الأداة برهانا لغويا.
كيف تصلحه؟
افحص قواعد العمر والمحاذاة والوصول مباشرة.
كيف تتجنب تكراره؟
وثق الالتزامات حتى عند صمت الفحوص الديناميكية.

اختبر فهمك

1. يبدأ حقل شبكة عند إزاحة بايت واحد. لماذا يتجنب المحلل تحويل عنوانه مباشرة إلى مؤشر عدد من 32 بتا؟

اعرض الحل المشروح

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

2. اشرح الأدلة اللازمة قبل قبول مخرج مزعوم لتعبير يعدل المتغير القياسي نفسه مرتين.

اعرض الحل المشروح

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

أهم ما تعلمته

  • لا يملك السلوك غير المعرّف مخرجا متوقعا قابلا للنقل.
  • لا ينشئ تحويل المؤشر كائنا ولا محاذاة ولا إذن وصول.
  • استخدم نسخ التمثيل وفق عقود صلاحية وتنسيق صريحة.

المراجع الرسمية