حجم
يُعدّ sizeof عاملًا أحاديًا في لغتي البرمجة C و C++ ، يُقيّم إلى حجم تخزين تعبيرأو نوع بيانات ، مُقاسًا بوحدات حجمها t. بالتالي، يُقيّم التعبيرإلىيُحددعدد بتات نوع البيانات بواسطة ماكرو المعالج المسبق ، المُعرّف في ملف التضمين القياسي <limits.h> . في معظم منصات الحوسبة الحديثة، يبلغ هذا العدد ثمانية بتات. نتيجة sizeofهي عدد صحيح غير مُوقّع، يُكتب عادةً كـ size_t .charsizeof(char)1charCHAR_BITsizeof
يقبل المعامل معاملًا واحدًا إما أن يكون نوع بيانات معبرًا عنه كتحويل - اسم نوع بيانات محاط بأقواس - أو تعبيرًا غير نوعي لا يتطلب أقواسًا.
غاية
تحتاج العديد من البرامج إلى معرفة حجم التخزين لنوع بيانات معين. على الرغم من أن حجم نوع البيانات ثابت في أي تطبيق للغة C أو C++، إلا أن أحجام الأنواع الأولية في C وC++ قد تختلف باختلاف منصات التنفيذ. على سبيل المثال، قد يستخدم تخصيص مساحة المصفوفة أثناء التشغيل الكود التالي، حيث يتم تطبيق عامل sizeof على تحويل النوع int:
int * pointer = ( int * ) malloc ( 10 * sizeof ( int ));في هذا المثال، malloc()تقوم الدالة بتخصيص مساحة في الذاكرة وإرجاع مؤشر إلى كتلة الذاكرة. حجم الكتلة المخصصة يساوي عدد البايتات اللازمة لكائن واحد من النوع intمضروبًا في 10، مما يوفر مساحة لعشرة أعداد صحيحة.
ليس من الآمن عمومًا افتراض حجم أي نوع بيانات. على سبيل المثال، مع أن معظم تطبيقات لغتي C وC++ على أنظمة 32 بت تُعرّف نوع البيانات intبأربعة بايتات، إلا أن هذا الحجم قد يتغير عند نقل الكود إلى نظام آخر، مما قد يؤدي إلى تعطله. الاستثناء من ذلك هو نوع البيانات `int` char، الذي يكون حجمه دائمًا `int` 1في أي تطبيق C متوافق مع المعايير. إضافةً إلى ذلك، يصعب غالبًا التنبؤ بأحجام أنواع البيانات المركبة مثل `int` structأو `int` union، بسبب الحشو. sizeofيُحسّن استخدام `int` من سهولة القراءة، لأنه يتجنب الثوابت العددية غير المسماة ( الأرقام السحرية ).
ينتج عن استخدام الشكل غير المرجعي للمؤشر إلى عنوان التخزين صيغة مكافئة لتخصيص نفس مساحة المصفوفة، وهذه المرة يتم تطبيق المعامل على متغير مؤشر:
int * pointer = ( int * ) malloc ( 10 * sizeof * pointer );يستخدم
يُنتج هذا المُعامل sizeofمساحة التخزين المطلوبة في الذاكرة للمُعامل الخاص به عند تجميع الكود. يُكتب المُعامل بعد الكلمة المفتاحية، sizeofوقد يكون رمزًا لمساحة تخزين، مثل مُتغير أو تعبير أو اسم نوع. الأقواس الخاصة بالمُعامل اختيارية، إلا عند تحديد اسم نوع. نتيجة هذا المُعامل هي حجم المُعامل بالبايت، أو حجم مساحة التخزين المطلوبة في الذاكرة. بالنسبة للتعبيرات، يُقيّم المُعامل إلى حجم تمثيل النوع الذي ينتج عن تقييم التعبير، وهو تقييم لا يتم تنفيذه.
على سبيل المثال، بما أن sizeof(char)قيمة تُعرَّف على أنها 1 [ 1 ]، وبافتراض أن نوع العدد الصحيح يتكون من أربعة بايتات، فإن جزء الكود التالي يطبع1.4:
شار ج ؛ printf ( "%zu,%zu \n " , sizeof c , sizeof ( int ));تُعرّف بعض ملفات الرأس القياسية، مثل <stddef.h> ، النوع size_t للدلالة على نوع العدد الصحيح غير المُوقّع لنتيجة تعبير sizeof . ويُستخدم مُحدِّد العرض z لتنسيق هذا النوع.printf()
sizeofلا يمكن استخدامها في تعبيرات المعالج المسبق للغة C ، مثل #if، لأنها عنصر من عناصر لغة البرمجة، وليست من عناصر بناء الجملة للمعالج المسبق أو وحدات الماكرو الخاصة به، والتي ليس لها أنواع بيانات.
يستخدم المثال التالي في لغة C++ عامل التشغيل sizeofمع القوالب المتغيرة.
template < typename ... Args > [[ nodiscard ]] size_t getSize ( Args && ... args ) noexcept { // الحصول على حجم حزمة المعاملات. size_t count = sizeof ...( Args ); return count ; }sizeofيمكن استخدامها مع القوالب المتغيرة في C++11 وما فوق على حزمة المعلمات لتحديد عدد الوسائط.
تطبيق على المصفوفات
عند sizeofتطبيق `$` على اسم مصفوفة، تكون النتيجة عدد البايتات اللازمة لتخزين المصفوفة بأكملها. هذا أحد الاستثناءات القليلة للقاعدة التي تنص على تحويل اسم المصفوفة إلى مؤشر إلى العنصر الأول فيها، وهو أمر ممكن لأن حجم المصفوفة الفعلي ثابت ومعروف وقت الترجمة ، عند sizeofتقييم المعامل. يستخدم البرنامج التالي `$` sizeofلتحديد حجم مصفوفة مُعلنة، متجنبًا بذلك تجاوز سعة المخزن المؤقت عند نسخ الأحرف:
#include <stdio.h>int main ( int argc , char * argv []) {// مصفوفة من 10 أحرفمخزن الأحرف [ 10 ]؛/* انسخ 9 أحرف كحد أقصى من argv[1] إلى المخزن المؤقت، * إنهاء المخزن المؤقت بصفر. */snprintf ( buffer , sizeof buffer , "%s" , argv [ 1 ]);أرجع 0 ؛}هنا، sizeof bufferيعادل 10 * sizeof buffer[0]، والذي يتم تقييمه إلى 10، لأن حجم النوع charمحدد على أنه 1.
يُضيف معيار C99 دعمًا لعناصر المصفوفات المرنة في الهياكل. يُسمح بهذا النوع من تعريف المصفوفات كعنصر أخير في الهياكل فقط، ويختلف عن المصفوفات العادية في عدم تحديد طول لها للمُصرّف . بالنسبة لهيكل باسم sيحتوي على عنصر مصفوفة مرن باسم a، فإنّ sizeof s يُعادل offsetof (s, a) :
#include <stdio.h>typedef struct {char val ;int a [];// عضو مصفوفة مرن؛ يجب أن يكون العنصر الأخير في البنية} FlexibleArray ;int main ( int argc , char * argv []) {printf ( "sizeof(FlexibleArray) == %zu \n " , sizeof ( FlexibleArray ));أرجع 0 ؛}في هذه الحالة، sizeofيُعيد المُعامل حجم البنية، بما في ذلك أي حشو، ولكن دون أي مساحة تخزين مُخصصة للمصفوفة. تُنتج معظم المنصات المخرجات التالية:
- حجم (FlexibleArray) يساوي 4
تسمح لغة C99 أيضًا باستخدام مصفوفات ذات أطوال متغيرة، حيث يُحدد طولها أثناء التشغيل، [ 2 ] على الرغم من أن هذه الميزة تُعتبر اختيارية في الإصدارات اللاحقة من معيار C. في هذه الحالات، sizeofيُقيّم المعامل جزئيًا أثناء التشغيل لتحديد مساحة التخزين التي تشغلها المصفوفة.
#include <stddef.h>size_t flexsize ( int n ) { char b [ n + 3 ]; // مصفوفة ذات طول متغير return sizeof b ; // وقت التنفيذ sizeof }int main ( void ) { size_t size = flexsize ( 10 ); // تُرجع الدالة flexsize القيمة 13 return 0 ; }sizeofيمكن استخدام دالة `sizeof` لتحديد عدد عناصر المصفوفة، وذلك بقسمة حجم المصفوفة بالكامل على حجم عنصر واحد. يجب استخدام هذه الدالة بحذر؛ فعند تمرير مصفوفة إلى دالة أخرى، ستتحول إلى مؤشر. في هذه الحالة، ستُرجع دالة `sizeof` حجم المؤشر، وليس الحجم الكلي للمصفوفة. كمثال على مصفوفة صحيحة:
int main ( void ) { int tab [ 10 ]; printf ( "عدد العناصر في المصفوفة: %zu \n " , sizeof tab / sizeof tab [ 0 ]); // ينتج 10 return 0 ; }أنواع غير مكتملة
sizeofلا يمكن تطبيق هذا إلا على الأنواع المُعرَّفة "بشكل كامل". بالنسبة للمصفوفات، يعني هذا أنه يجب تحديد أبعاد المصفوفة في تعريفها ، وأن يكون نوع عناصرها مُعرَّفًا بشكل كامل. بالنسبة للأنواع structs و unions، يعني هذا أنه يجب أن تكون هناك قائمة أعضاء من الأنواع المُعرَّفة بشكل كامل. على سبيل المثال، انظر إلى ملفي المصدر التاليين:
// File1.c int arr [ 10 ];struct IntegerPair { int first ; int second ; }; // المزيد من التعليمات البرمجية...// File2.c extern int arr []; struct IntegerPair ;// المزيد من التعليمات البرمجية...كلا الملفين مكتوبان بلغة C بشكل صحيح، ويمكن تطبيق الكود الموجود في الملف File1.c على المصفوفة ` a` و`b` . مع ذلك، يُعدّ تطبيق الكود الموجود في الملف File2.c على المصفوفة `a` غير قانوني ، لأن تعريفات المصفوفة `a` في الملف File2.c غير مكتملة. ففي حالة المصفوفة `a` ، لا يُحدد الكود أبعاد المصفوفة؛ وبدون هذه المعلومة، لا يستطيع المُصرّف معرفة عدد عناصر المصفوفة، وبالتالي لا يمكنه حساب حجمها الإجمالي. وبالمثل، لا يستطيع المُصرّف حساب حجم المصفوفة `b` لأنه لا يعرف مكوناتها، وبالتالي لا يمكنه حساب مجموع أحجام عناصرها (بما في ذلك الحشو). إذا حدد المبرمج حجم المصفوفة `a` في تعريفها في الملف File2.c ، أو أكمل تعريف المصفوفة `b` بتوفير قائمة بالعناصر، فسيُتيح ذلك تطبيق المصفوفة `a` على المصفوفة `a` أو `b` في ذلك الملف المصدر.sizeofarrstruct IntegerPairarrstruct IntegerPairstruct IntegerPairsizeofarrstruct IntegerPair
أعضاء الكائن
أتاح الإصدار C++11 إمكانية تطبيق المعامل sizeofعلى أعضاء محددين من فئة ما دون الحاجة إلى إنشاء كائن لتحقيق ذلك. [ 3 ] على سبيل المثال، يُظهر المثال التالي4و8على معظم المنصات.
استيراد std ؛struct IntegerPair { int first ; int second ; };int main () { std :: println ( "{}, {}" , sizeof ( IntegerPair :: first ), sizeof ( IntegerPair )); }حزم القوالب المتغيرة
قدمت لغة C++11 القوالب المتغيرة ؛ الكلمة المفتاحية sizeofمتبوعة بعلامة الحذف تُرجع عدد العناصر في حزمة المعلمات.
template < typename ... Args > void printSize ( Args ... args ) { std :: println ( "{} " , sizeof ...( args )); }int main () { printSize (); // يُخرج 0 printSize ( "هل هذه هي الإجابة" , 42 , true ); // يُخرج 3 }تطبيق
عند تطبيقها على نوع بيانات أو متغير ذي طول ثابت، تُقيّم التعبيرات التي تحتوي على عامل التشغيل sizeof أثناء ترجمة البرنامج، وتُستبدل بقيم نتائج ثابتة. وقد أدخل معيار C99 المصفوفات ذات الطول المتغير (VLAs)، مما استلزم تقييم هذه التعبيرات أثناء تنفيذ البرنامج. في كثير من الحالات، يمكن توثيق تفاصيل التنفيذ في وثيقة واجهة التطبيق الثنائية (ABI) الخاصة بالمنصة، والتي تحدد التنسيقات والحشو والمحاذاة لأنواع البيانات، والتي يجب على المترجم الالتزام بها.
حشوة هيكلية
عند حساب حجم أي نوع من أنواع الكائنات، يجب على المترجم مراعاة أي محاذاة مطلوبة لبنية البيانات لتحقيق الكفاءة أو الالتزام بقيود البنية. لا تدعم العديد من بنى الحواسيب الوصول إلى بايتات متعددة بدءًا من أي عنوان بايت ليس من مضاعفات حجم الكلمة، وحتى عندما تسمح البنية بذلك، عادةً ما يستطيع المعالج جلب كائن محاذٍ للكلمة أسرع من جلب كائن يمتد على عدة كلمات في الذاكرة. [ 4 ] لذلك، عادةً ما يقوم المترجم بمحاذاة هياكل البيانات على الأقل إلى حدود كلمة ، كما يقوم بمحاذاة العناصر الفردية إلى حدودها الخاصة. في المثال التالي، Studentمن المرجح أن تتم محاذاة البنية على حدود كلمة، وهي أيضًا نقطة بداية العنصر grade، ومن المرجح أن يبدأ العنصر ageعند عنوان الكلمة التالي. يحقق المترجم ذلك الأخير عن طريق إدراج بايتات حشو بين العناصر حسب الحاجة لتلبية متطلبات المحاذاة. قد يكون هناك أيضًا حشو في نهاية البنية لضمان المحاذاة الصحيحة في حال استخدام البنية كعنصر في مصفوفة.
وبالتالي، يمكن أن يكون الحجم الإجمالي لبنية ما في لغة C أكبر من مجموع أحجام أعضائها الفردية. على سبيل المثال، في العديد من الأنظمة، يطبع الكود التالي8:
struct Student { char grade ; // char طوله بايت واحد int age ; // int طوله 4 بايت };printf ( "%zu " , sizeof ( struct Student ));انظر أيضاً
مراجع
- ↑ "معيار C99 (ISO/IEC9899)" (ملف PDF) . ISO/IEC . 7 سبتمبر 2007. 6.5.3.4.3، صفحة 80. تم الاطلاع عليه في 31 أكتوبر 2010 .
- ↑ "مسودة لجنة WG14/N1124 لمعيار ISO/IEC 9899" (ملف PDF) . 6 مايو 2005. 6 مايو 2005. 6.5.3.4 حجم المشغل.
- ↑ "N2253 توسيع sizeof ليشمل أعضاء البيانات غير الثابتة بدون كائن (مراجعة 1)" .
- ↑ رينتزش، جوناثان (8 فبراير 2005). "محاذاة البيانات: استقم وانطلق نحو اليمين" . آي بي إم . تم الاطلاع عليه بتاريخ 29 سبتمبر 2014 .
- لغة البرمجة سي
- لغة سي++
- المشغلون (البرمجة)
- العمليات الأحادية
