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

C / الأساسي: قراءة البرامج وبناؤها / الدرس 6

المصفوفات والنصوص والحدود

ميز السعة التخزينية للمتتالية من طولها المنطقي، وتعامل مع النهاية الصفرية كثابت صريح.

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

أهداف الدرس

  • فهرسة المصفوفات بحدود نصف مفتوحة.
  • شرح تحول المصفوفة إلى مؤشر وضياع معلومات الحجم.
  • الحفاظ على متطلبات سعة النص ونهايته.

السعة والطول يجيبان عن سؤالين مختلفين

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

استخدم المجال نصف المفتوح [0, count) لشرح الحلقات. أثبت قبل كل وصول انتماء الفهرس إلى هذا المجال. لا تجري C فحص حدود تلقائيا عاما. والوصول إلى عنصر بعد نهاية المصفوفة غير صالح حتى لو بدت الذاكرة المجاورة قابلة للقراءة في تجربة واحدة.

تمرير الطول مع البيانات

تتحول المصفوفة في معظم التعابير إلى مؤشر لعنصرها الأول. ويُعدل معامل الدالة المكتوب int values[] إلى معامل مؤشر، فلا يحمل طول مصفوفة المستدعي. مرر عددا منفصلا ووثق المجال المقروء. يحسب sizeof array / sizeof array[0] عدد العناصر فقط حيث تبقى array كائن مصفوفة فعليا.

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

c عد مصفوفة فعلية واجتيازها
#include <stddef.h>
#include <stdio.h>

int main(void)
{
    int values[] = {3, 1, 4, 2};
    size_t count = sizeof values / sizeof values[0];
    int total = 0;
    for (size_t i = 0; i < count; ++i) total += values[i];
    printf("count=%zu sum=%d\n", count, total);
    return 0;
}
الناتج المتوقع
count=4 sum=10

تحتوي المصفوفة أربعة عناصر int وتقرأ الحلقة كلا منها مرة واحدة.

النص يحتاج نهاية يمكن الوصول إليها

نص C متتالية محارف تنتهي عند أول محرف صفري. تشغل النهاية مساحة لكنها لا تدخل في strlen. تنشئ char name[] = "Ada" أربعة عناصر، بينما يسمح بتهيئة char name[3] = "Ada" لكنها لا تترك نهاية صفرية، ومن ثم لا تنشئ نص C.

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

البناء عندما تتسع الوجهة فقط

يحسب المثال الثاني طول المصدر ويتحقق من وجود مساحة لهذا الطول ونهايته، ثم ينسخ هذا العدد من البايتات بالضبط. المصدر نص معروف الصلاحية، لذلك يبرر استعمال strlen. يستخدم الفحص length >= capacity لتجنب حساب length زائد واحد في الحارس؛ ولهاتين المصفوفتين الفعليتين يكون طول النسخ اللاحق آمنا.

ليست strncpy بديلا آمنا شاملا لـstrcpy؛ فقد تحذف النهاية وقد تملأ الوجهة بمحارف إضافية. تعتمد الأداة المناسبة على العقد. قد يناسب النسخ الكامل المفحوص أو عملية بايتات بطول صريح أو snprintf مع فحص نتيجة البتر متطلبات مختلفة.

c نسخ نص كامل بعد فحص السعة
#include <stdio.h>
#include <string.h>

int main(void)
{
    const char source[] = "Ada";
    char destination[8];
    size_t length = strlen(source);
    if (length >= sizeof destination) return 1;
    memcpy(destination, source, length + 1);
    printf("%s (%zu)\n", destination, length);
    return 0;
}
الناتج المتوقع
Ada (3)

تُنسخ أربعة بايتات بما فيها النهاية؛ أما الطول المنطقي المبلغ فهو ثلاثة.

طول النص يعتمد على التمثيل

تعد strlen وحدات char قبل النهاية، ولا تعد الحروف المرئية للمستخدم أو نقاط Unicode. قد تشغل كلمة UTF-8 عدة بايتات لكل محرف ظاهر. لا تجعل اللغة الأساسية كل مجموعة محارف تنفيذ UTF-8، لذلك وثق الترميز عند حدود الإدخال والإخراج. وتوقف البايتات الصفرية الداخلية دوال النص المعتادة مبكرا أيضا.

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

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

تقرأ الحلقة العنصر عند الفهرس count.

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

تعطي sizeof داخل الدالة طول مصفوفة خاطئا.

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

يسبب اسم من ثلاثة أحرف قراءة نص خارج الحدود.

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

اختبر فهمك

1. لسعة وجهة أربعة، قرر هل يلائم "Ada" و"Alan" كنصين كاملين، وأظهر المقارنة.

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

طول "Ada" ثلاثة، ولذلك 3 < 4 ويلائم نسخ أربعة بايتات بما فيها النهاية بالضبط. طول "Alan" أربعة، ولذلك 4 >= 4 ويجب رفضه وفق عقد النسخ الكامل. لا يفي نسخ أربعة بايتات فقط من "Alan" بالعقد لغياب النهاية. البتر سلوك مختلف يحتاج سياسة صريحة مستقلة.

2. اشرح اجتياز متتالية منطقية من صفر عناصر مخزنة في int storage[8] ولماذا لا تلزم قراءة storage[0].

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

اجعل count صفرا واستخدم for (size_t i = 0; i < count; ++i). يفشل الشرط الأول، فلا يُقرأ أي عنصر. تبقى السعة التخزينية ثمانية لكن المتتالية المنطقية فارغة. يمكن لاختزال مثل الجمع إعادة عنصره المحايد صفر؛ أما دالة القيمة القصوى فتحتاج سياسة فشل صريحة للمدخل الفارغ.

أهم ما تعلمته

  • افصل السعة والطول المنطقي والنهاية النصية.
  • لا تحفظ معاملات المصفوفة حجم مصفوفة المستدعي.
  • كل وصول مفهرس يحتاج مجالا صالحا؛ وكل عملية نصية تحتاج عقد انتهاء.

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