C / المتوسط: الذاكرة والمكونات الموثوقة / الدرس 7
المؤشرات وعمر الكائن والأسماء البديلة
حلل ما يجوز للمؤشر تحديده ومدة وجود الكائن والعمليات التي تبقى ضمن حدوده.
في هذه الصفحة
أهداف الدرس
- تمييز قيم المؤشرات والكائنات المشار إليها والمؤشر الصفري.
- شرح النطاق ومدة التخزين والعمر.
- الحفاظ على مجالات المؤشرات وعقود الوصول const.
العنوان جزء من الصلاحية فقط
قد تحدد قيمة المؤشر كائنا أو موضعا مسموحا بعد النهاية مباشرة، أو لا تحدد كائنا عبر القيمة الصفرية. يتطلب فك الإشارة أكثر من عنوان يبدو عدديا وغير صفري: يجب أن يكون الهدف حيا ومحاذى ومحدودا ويُوصل إليه بنوع مسموح. لا يحمل المؤشر شهادة تشغيل عامة بهذه الخصائص.
يحصل المعامل الأحادي & على عنوان الكائن، بينما يصل * الأحادي إلى الكائن المحدد. في int *p تنتمي النجمة إلى التصريح؛ وفي *p = 9 تكون عملية. يساعد رسم كائن المؤشر والكائن الهدف منفصلين على تمييز تغيير p من تغيير *p.
تعديل الكائن المقصود
يشير المثال الكامل الأول إلى int محلي حي ويحدثه عبر ذلك المؤشر. يبقى الاسمان دالين على كائن int الأساسي نفسه عند الملاحظة. تُنسخ قيمة المؤشر عند تمريرها إلى دالة، فيستطيع المساعد تعديل الكائن المحدد دون تعديل متغير المؤشر لدى المستدعي.
يعني تعدد الأسماء البديلة أن عدة تعابير قد تحدد الكائن نفسه. قد يكون ذلك مقصودا، لكن يجب على دالة ذات معاملي مؤشر وصف هل يجوز أن يتطابق هدفاهما. مثلا ينبغي لعملية التبديل معالجة الإشارة إلى الكائن نفسه أو حظرها صراحة ضمن شروطها المسبقة.
#include <stdio.h>
static void set_to_nine(int *target)
{
*target = 9;
}
int main(void)
{
int value = 4;
int *pointer = &value;
set_to_nine(pointer);
printf("%d %d\n", value, *pointer);
return 0;
}
9 9
تتطلب الدالة int حيا قابلا للكتابة؛ وتوفره main فتلاحظ القراءتان تسعة.
معرفة نهاية وجود الهدف
للكائن المحلي العادي مدة تخزين تلقائية، وينتهي عمره عند مغادرة كتلته. لا تمدد إعادة عنوانه ذلك العمر. أما كائن نطاق الملف أو المحلي static فله مدة تخزين ساكنة ويستمر طوال تنفيذ البرنامج، مع أن مشاركة هذه الحالة تثير اعتبارات تصميم أخرى.
يستمر التخزين المخصص حتى تحريره بعملية الإلغاء المناسبة. قد يبقى المؤشر ضمن النطاق بعد انتهاء عمر هدفه، لذلك لا يعني ظهور الاسم الصلاحية. وتعامل C17 قيمة المؤشر كقيمة غير محددة عندما ينتهي عمر الكائن الذي يشير إليه أو الموضع الواقع بعده مباشرة. لا تستخدم فحص مؤشر متدل استراتيجية للتحقق.
اجتياز مصفوفة واحدة لا ذاكرة عشوائية
تُعرّف حسابات المؤشرات داخل كائن مصفوفة، ويجوز أن تنتج مؤشرا بعد النهاية مباشرة. يفيد ذلك المؤشر موضع توقف لكنه لا يجوز فك إشارته. يتطلب طرح المؤشرات مواضع داخل المصفوفة نفسها وفرقا يمكن تمثيله في ptrdiff_t. وليست المقارنة الترتيبية ترتيبا عاما لكائنات مخصصة غير مترابطة.
يبقي الاجتياز التالي p بين العنصر الأول والموضع التالي للنهاية. تسبق المقارنة كل فك إشارة، فينهي p == end الاجتياز فورا. يكافئ ذلك حلقة فهرسة محدودة. لا تلغي صياغة المؤشر ضرورة حمل مدى المصفوفة كجزء من الواجهة.
#include <stdio.h>
int main(void)
{
const int values[] = {2, 4, 6};
const int *end = values + 3;
int total = 0;
for (const int *p = values; p != end; ++p) total += *p;
printf("sum=%d\n", total);
return 0;
}
sum=12
تقرأ الحلقة ثلاثة عناصر صالحة ولا تفك إشارة end مطلقا.
التعبير عن أذونات الوصول
تمنع const int *p تعديل int عبر p؛ وتمنع int *const p إسناد قيمة مؤشر أخرى إلى p. ولا تثبت أي الصيغتين أن اسما بديلا آخر لا يستطيع تعديل الهدف. لذلك يعد مسار الوصول const وعدا للواجهة بشأن العمليات المسموحة، وليس لقطة تلقائية أو ضمان تزامن.
عند مراجعة معامل مؤشر اطرح خمسة أسئلة ملموسة: أي كائن يحدده، وكم عنصرا يمكن الوصول إليه، وهل الكتابة مسموحة، وإلى متى يستمر الوصول، وهل تحتفظ الدالة بالمؤشر. تحول هذه الأسئلة واجهة عناوين مبهمة إلى عقد يستطيع المستدعون الوفاء به.
الأخطاء الشائعة وإصلاحها
تعيد الدالة عنوان متغيرها المحلي.
- لماذا يحدث؟
- ينتهي عمر الهدف عند الإرجاع.
- كيف تصلحه؟
- أعد القيمة أو اكتب في تخزين يملكه المستدعي.
- كيف تتجنب تكراره؟
- سجل النطاق أو المالك الذي يبقي كل هدف حيا.
يتبع فحص عدم الصفر وصول غير صالح.
- لماذا يحدث؟
- اعتُبر عدم الصفر صلاحية كاملة للمؤشر.
- كيف تصلحه؟
- أعد إثبات العمر والمدى والمحاذاة والنوع.
- كيف تتجنب تكراره؟
- تحقق من الملكية والحدود عند إنشاء المؤشرات.
يُقرأ مؤشر النهاية كعنصر.
- لماذا يحدث؟
- اختلط موضع التوقف بالكائن الصالح.
- كيف تصلحه؟
- قارن قبل فك الإشارة وتوقف عند end.
- كيف تتجنب تكراره؟
- استخدم المجالات نصف المفتوحة باتساق في الوثائق والحلقات.
اختبر فهمك
1. اشرح صلاحية إعادة قيمة int وعدم صلاحية إعادة عنوان int محلي لفك إشارته لاحقا.
اعرض الحل المشروح
تُنسخ القيمة الحسابية المعادة إلى سياق المستدعي قبل انتهاء الدالة، ولا تعتمد على استمرار الكائن المحلي. أما العنوان فيظل مرتبطا بتخزين محلي ينتهي عمره عند الإرجاع. لا يجوز للمستدعي فك إشارته. استخدم قيمة معادة أو كائن إخراج يوفره المستدعي أو ملكية تخصيص صريحة.
2. عدل فكرة الاجتياز لمتتالية منطقية فارغة وحدد شرط المؤشر دون حسابات على المؤشر الصفري.
اعرض الحل المشروح
عند العدد صفر أعد المجموع المحايد قبل إنشاء مؤشر نهاية إذا سمحت الواجهة بصفري للمدخل الفارغ. وعند عدد غير صفري اشترط مؤشرا لأول count كائنات int قابلة للقراءة على الأقل داخل مصفوفة واحدة. بعدها فقط أنشئ begin + count واجتز. يميز ذلك اصطلاح الصفري للفارغ المريح من السماح بحسابات عشوائية على مؤشر صفري.
أهم ما تعلمته
- تشمل صلاحية المؤشر العمر والحدود والمحاذاة ونوع الوصول.
- الموضع التالي للنهاية حد وليس عنصرا.
- وثق الاحتفاظ بالمؤشرات وتعدد الأسماء بدقة أنواع المعاملات نفسها.