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

C / المتوسط: الذاكرة والمكونات الموثوقة / الدرس 8

التخصيص الديناميكي والملكية

صمم التخصيص وتغيير الحجم والتنظيف كبروتوكول ملكية واحد يشمل جميع مسارات الفشل.

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

أهداف الدرس

  • فحص أحجام التخصيص قبل الضرب.
  • حفظ الملكية عند فشل realloc.
  • فصل المالكين من المؤشرات المستعارة مؤقتا.

المورد يحتاج مالكا مسؤولا

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

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

فحص الحجم قبل التخصيص

عند تخصيص count عناصر يجب أن يكون طلب البايتات count * sizeof *pointer قابلا للتمثيل في size_t قبل أن تراه malloc. افحص count > SIZE_MAX / sizeof *pointer قبل الضرب. قد يخصص الحجم الملتف مساحة أقل مما تفترضه حلقة العناصر اللاحقة. حدد أيضا سياسة العدد صفر صراحة بدلا من الاعتماد على سلوك تخصيص حجم صفري.

يخصص المثال الأول ثلاثة كائنات int ويهيئ كل عنصر قبل قراءته. لا يلزم تحويل صريح لنتيجة malloc في C لأن مؤشرات void تتحول إلى أنواع مؤشرات الكائنات؛ ويجب تضمين stdlib.h للتصريح الصحيح. يختلف ذلك عن C++، وقد يخفي نقل صياغة التخصيص أو عادات التحويل منها أخطاء.

c التخصيص والتهيئة والاستخدام والتحرير
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    size_t count = 3;
    int *values = NULL;
    if (count > SIZE_MAX / sizeof *values) return 1;
    values = malloc(count * sizeof *values);
    if (values == NULL) return 1;
    for (size_t i = 0; i < count; ++i) values[i] = (int)i + 1;
    printf("last=%d\n", values[count - 1]);
    free(values);
    return 0;
}
الناتج المتوقع
last=3

عند نجاح التخصيص تكون قيمة آخر عنصر مهيأ ثلاثة، ويحرر المالك الوحيد التخزين.

تغيير الحجم دون فقدان المالك

قد تنقل realloc التخصيص. عند طلب غير صفري يعيد الفشل صفريا ويبقي التخصيص الأصلي متاحا؛ أسند النتيجة إلى مؤشر مؤقت أولا. وعند النجاح استخدم المؤشر المعاد واعتبر المؤشرات السابقة داخل التخصيص القديم غير صالحة، حتى لو بدا العنوان المعاد دون تغيير. التخزين المضاف غير مهيأ.

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

حفظ مسار الفشل

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

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

c تغيير حجم غير صفري مع حفظ الملكية
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *values = malloc(2 * sizeof *values);
    if (values == NULL) return 1;
    values[0] = 10; values[1] = 20;
    int *temporary = realloc(values, 4 * sizeof *values);
    if (temporary == NULL) { free(values); return 1; }
    values = temporary;
    values[2] = 30; values[3] = 40;
    printf("first=%d last=%d\n", values[0], values[3]);
    free(values);
    return 0;
}
الناتج المتوقع
first=10 last=40

يحفظ مسار النجاح العنصرين الأولين ويهيئ المضافين صراحة.

تصميم التنظيف قبل النجاح

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

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

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

يسرب فشل realloc الكتلة الأصلية.

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

ينتج عدد عناصر كبير تخصيصا ضئيلا.

لماذا يحدث؟
التف ضرب الحجم البايتي في size_t.
كيف تصلحه؟
افحص حد القسمة قبل الضرب.
كيف تتجنب تكراره؟
اختبر حراس الحجم مستقلة دون محاولة تخصيصات هائلة.

يفشل مؤشر عنصر مستعار بعد التوسع.

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

اختبر فهمك

1. صف الحالة بعد كل نقطة فشل في دالة تخصص اسما ثم مصفوفة أعداد صحيحة.

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

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

2. لماذا تعد malloc(count * sizeof pointer) خاطئة لمصفوفة من الكائنات المشار إليها؟ أعط القاعدة الصحيحة.

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

تقيس sizeof pointer كائن المؤشر، وقد يختلف عن حجم العنصر. استخدم sizeof *pointer لقياس النوع المحدد، ثم ارفض count > SIZE_MAX / sizeof *pointer قبل حساب الضرب. اختر سياسة مستقلة للعدد صفر. وتتبع هذه الصيغة أيضا تغيير نوع العنصر لاحقا دون تكرار اسمه في تعبير التخصيص.

أهم ما تعلمته

  • الملكية بروتوكول يشمل النجاح والفشل والنقل والتحرير.
  • افحص أعداد البايتات قبل أن تلتف الحسابات.
  • يبطل تغيير التخصيص المؤشرات المستعارة السابقة ويحتاج خطوة اعتماد.

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