Menu
flag Ar iconالعربيةdown icon

المصفوفات في لغة C: التعريف والتهيئة والمرور عليها

كيفية تعريف المصفوفات وتهيئتها في لغة C، وفهرستها من الصفر، وحساب الطول بـ sizeof، والمرور على العناصر، ولماذا تُعدّ القراءة بعد النهاية سلوكًا غير معرَّف بدل أن تكون رسالة خطأ.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

المصفوفة كتلة ثابتة الحجم من عناصر لها النوع نفسه، موضوعة واحدًا تلو الآخر في الذاكرة. هذا الترتيب المتّصل هو القصة كلها: فهو سبب كون الفهرسة فورية، وسبب الترابط الوثيق بين المصفوفات والمؤشّرات، وسبب قدرة C على تسليمك عنوان العنصر صفر وتركك تعثر على البقية بالحساب.

تعريف مصفوفة

نوع العنصر، والاسم، وحجم بين قوسين معقوفين:

int    scores[5];       // 5 أعداد صحيحة
double prices[100];     // 100 عدد من نوع double
char   initials[3];     // 3 محارف

الحجم هو عدد العناصر، ويجب أن يكون معروفًا في موضع تعريف المصفوفة - عادةً قيمة حرفية أو ثابت معرَّف بـ #define. تُحجَز الذاكرة فورًا؛ ولا يُخصَّص شيء لاحقًا.

تحتوي مصفوفة محلية حديثة التعريف على قيم مهملة، لا على أصفار:

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

التهيئة

عدة صيغ، كلها مفيدة:

صيغة {0} هي التي يجدر تذكّرها: أي عناصر لا تذكرها تُهيَّأ بالصفر، فصفر واحد يصفّر المصفوفة كلها. أما المُهيِّئات المعيَّنة ([4] = 99) فتتيح لك ضبط مواضع محدّدة وترك البقية عند الصفر، وهو أمر مفيد لجداول البحث الفارغة في معظمها.

الشيء الوحيد الذي لا يمكنك فعله هو إسناد مصفوفة بعد تعريفها:

int a[3] = {1, 2, 3};
int b[3];
b = a;                    // خطأ: المصفوفة غير قابلة للإسناد
memcpy(b, a, sizeof a);   // هكذا تنسخ واحدة

الفهرسة تبدأ من الصفر

العنصر الأول هو arr[0] والأخير هو arr[n-1]. ولا وجود لـ arr[n].

شغّل تلك الحلقة الأخيرة وانظر إلى العناوين: كل عنوان يبعد عن سابقه بمقدار sizeof(int) بايت بالضبط. العناصر موضوعة فعلًا طرفًا إلى طرف:

 index      0         1         2         3         4
         +--------+--------+--------+--------+--------+
 value   |   88   |   95   |   75   |   60   |  100   |
         +--------+--------+--------+--------+--------+
 offset   +0       +4       +8       +12      +16      bytes
          ^
          &scores[0], which is also what `scores` decays to

الفهرسة من الصفر ليست اختيارًا اعتباطيًا - فهي تجعل الفهرس إزاحة من البداية، وهو بالضبط ما يحتاجه حساب العناوين. يُعرَّف scores[i] بأنه *(scores + i)؛ انظر المؤشّرات والمصفوفات.

إيجاد الطول

لا تخزّن C طول المصفوفة في أي مكان أثناء التشغيل، لكن المترجم يعرفه، وsizeof يمنحك الوصول إليه:

sizeof(arr) / sizeof(arr[0]) هي الصيغة الاصطلاحية. والقسمة على arr[0] بدلًا من sizeof(double) المكتوب يدويًا تعني أن السطر يستمرّ في العمل إن غيّرت نوع العنصر.

الفخّ: هذا يعمل فقط حيث عُرّفت المصفوفة. مرّر المصفوفة إلى دالة فيصبح الوسيط مؤشّرًا، ويقيس sizeof المؤشّر بدلًا منها - عادةً 8 بايت، فيعطي "طولًا" مقداره 1 أو 2. ولهذا فإن كل دالة في C تأخذ مصفوفة تأخذ معها عددًا أيضًا:

المرور على مصفوفة

الشكل القياسي هو حلقة for تبدأ من 0 ما دام i < n:

لاحظ i < n، لا i <= n. مع n عنصرًا تكون الفهارس الصالحة من 0 إلى n - 1، فيُنفّذ <= مرورًا إضافيًا ويقرأ ما بعد النهاية. وتهيئة hottest وcoldest من temps[0] بدلًا من 0 هي العادة الأخرى الجديرة بالنسخ - فبدء قيمة عظمى من الصفر ينكسر بصمت مع بيانات كلها سالبة.

تجاوز الحدود سلوك غير معرَّف

هذا هو الجزء من لغة C الذي يفاجئ القادمين من لغات أخرى. لا يوجد فحص للحدود. لا عند الترجمة ولا عند التشغيل. arr[10] على مصفوفة من خمسة عناصر ليس خطأً - بل هو حساب عنوان يجريه المترجم دون تعليق.

int arr[5] = {1, 2, 3, 4, 5};
arr[7] = 99;     // يكتب 8 بايت بعد نهاية المصفوفة
int x = arr[-1]; // يقرأ قبل البداية

ما يحدث بعد ذلك هو سلوك غير معرَّف، وأعراضه متنوّعة بشكل غير مفيد:

  • يبدو وكأنه يعمل، لأن الذاكرة التي أصابها كانت غير مستخدمة؛
  • يتغيّر متغيّر آخر دون سبب ظاهر؛
  • ينهار البرنامج بـ خطأ تجزئة، ربما بعد وقت طويل؛
  • يختلف السلوك بين -O0 و-O2.

"يبدو وكأنه يعمل" هو الخطر الأكبر، لأنه يعني أن العلّة تُشحن مع المنتج. ثلاثة خطوط دفاع:

  1. كرّر بـ i < n. معظم التجاوزات أخطاء انزياح بمقدار واحد في شرط حلقة.
  2. تحقّق من الفهارس القادمة من الإدخال.
  1. ابنِ بأداة تعقيم أثناء التطوير. يحوّل gcc -fsanitize=address -g prog.c معظم التجاوزات إلى تقرير فوري يسمّي الملف والسطر والمصفوفة المعنيّة.

مصفوفات من أنواع أخرى

الصياغة نفسها تعمل مع أي نوع عنصر، بما في ذلك البنى:

سطر char word[6] = "hello"; يستحق وقفة: السلسلة النصية في C ليست سوى مصفوفة char آخر بايت ذي معنى فيها هو '\0'. ذلك المحرف المُنهي هو سبب حاجة المصفوفة إلى ستة مواضع لخمسة حروف.

المصفوفات متغيّرة الطول، باختصار

يسمح C99 بمصفوفة حجمها قيمة تُحسب أثناء التشغيل:

int n = get_count();
int buffer[n];        // مصفوفة متغيّرة الطول

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

عندما يكون الحجم مجهولًا فعلًا حتى وقت التشغيل، خصّص الذاكرة بدلًا من ذلك:

لاحظ أن arr[i] تُقرأ بالشكل نفسه سواء كانت arr مصفوفة أو مؤشّرًا إلى ذاكرة مخصّصة - فصياغة الفهرسة لا تأبه بذلك. التفاصيل الكاملة في الذاكرة الديناميكية.

الأسئلة الشائعة

كيف تعرّف مصفوفة في لغة C؟

اذكر نوع العنصر واسمًا وحجمًا بين قوسين معقوفين: int scores[5]; يحجز مكانًا لخمسة أعداد صحيحة. يجب أن يكون الحجم ثابتًا معروفًا في موضع تعريف المصفوفة (باستثناء المصفوفات متغيّرة الطول)، وتبدأ العناصر غير مهيّأة ما لم تزوّدها بقيم.

كيف تجد طول مصفوفة في لغة C؟

sizeof(arr) / sizeof(arr[0]) - الحجم الكلي بالبايت مقسومًا على حجم عنصر واحد. يعمل هذا فقط في النطاق الذي عُرّفت فيه المصفوفة: فبمجرّد تمريرها إلى دالة تكون قد تحوّلت إلى مؤشّر ويقيس sizeof المؤشّر بدلًا منها.

ماذا يحدث إذا تجاوزت نهاية مصفوفة في لغة C؟

لا شيء يوقفك. لا تُجري C أي فحص للحدود، لذا فإن arr[10] على مصفوفة من خمسة عناصر تقرأ أو تكتب في أي ذاكرة تصادف وجودها هناك. هذا سلوك غير معرَّف: قد يطبع قيمًا مهملة، أو يُفسد متغيّرًا آخر، أو يتسبّب بانهيار - وقد يبدو وكأنه يعمل إلى أن يتوقّف عن ذلك.

كيف تهيّئ كل عناصر مصفوفة بالصفر في لغة C؟

int arr[100] = {0}; - أي عناصر لا تذكرها تُهيَّأ بالصفر، فصفر واحد يضبط المصفوفة كلها. كما يعمل int arr[100] = {}; في C23. ومن دون أي مُهيِّئ، تكون محتويات مصفوفة محلية قيمًا مهملة غير محدّدة.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن