إزاحة

يُعدّ الماكرو في لغة C ميزةً من ميزات مكتبة ANSI C، وهو موجود في الملف stddef.h . يُقيّم هذا الماكرو إلى إزاحة (بالبايت) عنصر مُحدد ضمن بنية أو نوع اتحاد ، وهو تعبير من النوع size_t . يأخذ الماكرو مُعاملين ، الأول هو اسم البنية أو الاتحاد، والثاني هو اسم كائن فرعي من البنية/الاتحاد ليس حقل بت . لا يُمكن وصفه بأنه نموذج أولي للغة C. [ 1 ]offsetof()offsetof()

تطبيق

يعتمد التنفيذ "التقليدي" للماكرو على حصول المترجم على إزاحة عضو من خلال تحديد بنية افتراضية تبدأ من العنوان صفر:

#define offsetof(st, m) \  ((size_t)&(((st*)0)->m))

يمكن فهم هذا على أنه أخذ مؤشر فارغ من نوع بنية st ، ثم الحصول على عنوان العضو m داخل تلك البنية. مع أن هذا التنفيذ يعمل بشكل صحيح في العديد من المترجمات، إلا أنه أثار بعض الجدل حول ما إذا كان هذا سلوكًا غير مُعرَّف وفقًا لمعيار لغة C، [ 2 ] نظرًا لأنه يبدو أنه يتضمن إلغاء مرجعية مؤشر فارغ . كما أنه يميل إلى إنتاج تشخيصات مُربكة من المترجم إذا كان أحد الوسائط مكتوبًا بشكل خاطئ.

والبديل هو:

#define offsetof(st, m) \  ((size_t)((char*)&((st*)0)->m - (char*)0))

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

تقوم بعض المترجمات الحديثة (مثل GCC ) بتعريف الماكرو باستخدام شكل خاص (كامتداد للغة) بدلاً من ذلك، على سبيل المثال [ 3 ]

#define offsetof(st, m) \  __builtin_offsetof(st, m)

تُعد هذه الخاصية المدمجة مفيدة بشكل خاص مع فئات C++ التي تُعلن عن عامل أحادي مخصص & . [ 4 ]

الاستخدام

يُعد هذا مفيدًا عند تنفيذ هياكل البيانات العامة في لغة C. على سبيل المثال، تستخدم نواة لينكس الدالة offsetof() لتنفيذ الدالة container_of() ، مما يسمح لنوع مثل mixin بالعثور على البنية التي تحتوي عليه: [ 5 ]

#define CONTAINER_OF(ptr, Type, member) ({ \  const typeof(((Type*)0)->member) *__mptr = (ptr); \  (Type*)((char*)__mptr - offsetof(Type, member));})

يُستخدم هذا الماكرو لاسترداد بنية محيطة من مؤشر إلى عنصر متداخل، مثل هذا التكرار لقائمة مرتبطة من كائنات MyStruct :

struct MyStruct { const char * name ; struct ListNode list ; };البنية الخارجية ListNode * listNext ( struct ListNode * );struct ListNode * current = /* ... */ ;بينما ( الحالي ) { بنية MyStruct * عنصر = حاوية_من ( الحالي ، بنية MyStruct ، قائمة طباعة ( "%s \n " ، عنصر -> الاسم الحالي = قائمة_التالي ( & عنصر -> القائمة }

يستخدم تطبيق نواة لينكس للدالة container_of إضافةً من لغة GNU C تُسمى تعابير العبارات . [ 6 ] من المحتمل أن يكون استخدام تعبير عبارة قد استُخدم لضمان سلامة النوع، وبالتالي تجنب الأخطاء العرضية المحتملة. مع ذلك، توجد طريقة لتنفيذ السلوك نفسه دون استخدام تعابير العبارات مع ضمان سلامة النوع في الوقت نفسه.

#define CONTAINER_OF(ptr, Type, member) ((Type*)((char*)(1 ? (ptr) : &((Type*)0)->member) - offsetof(Type, member)))

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

#define CONTAINER_OF(ptr, Type, member) ((Type*)((char*)(ptr) - offsetof(Type, member)))

هذا التنفيذ سيخدم نفس الغرض أيضًا، ومع ذلك، فإنه يحتوي على إغفال أساسي مقارنة بتنفيذ نواة لينكس الأصلية: ptrلا يتم التحقق من نوع مقابل نوع العضو.

في التنفيذ المذكور سابقًا الذي تم فيه التحقق من النوع، يتم إجراء التحقق باستخدام عامل الشرط بطريقة غير معتادة. تنص قيود عامل الشرط على أنه إذا كان كلا المعاملين مؤشرين إلى نوع واحد، فيجب أن يكونا مؤشرين إلى نوعين متوافقين. في هذه الحالة، على الرغم من أن قيمة المعامل الثالث للتعبير الشرطي لن تُستخدم أبدًا، يجب على المُصرّف إجراء فحص للتأكد من أن (ptr)كلا &((Type*)0)->memberالمؤشرين متوافقان.

القيود

offsetofيقتصر استخدام الدالة offsetof على أنواع البيانات البسيطة (POD) في C++98 ، وفئات التخطيط القياسي في C++11 ، [ 7 ] وتُدعم حالات أخرى بشكل مشروط في C++17 ، [ 8 ] وإلا فإن سلوكها غير مُحدد. مع أن معظم المُترجمات تُنتج نتيجة صحيحة حتى في الحالات التي لا تلتزم بالمعيار، إلا أن هناك حالات استثنائية تُنتج فيها offsetof قيمة غير صحيحة، أو تُصدر تحذيرًا أو خطأً أثناء الترجمة، أو تُؤدي إلى تعطل البرنامج. ينطبق هذا بشكل خاص على الوراثة الافتراضية. [ 9 ] سيُصدر البرنامج التالي عدة تحذيرات ويطبع نتائج مشبوهة بشكل واضح عند ترجمته باستخدام gcc 4.7.3 على معمارية amd64:

#include <cstddef>استيراد std ؛struct Base { int a ; virtual void dummy () {} };struct Derived : virtual public Base { int b ; };int main () { std :: println ( "offsetof(Base, a) : {}" , offsetof ( Base , a )); std :: println ( "offsetof(Derived, b) : {}" , offsetof ( Derived , b )); return 0 ; }

الناتج هو:

offsetof(Base, a) : 8 offsetof(Derived, b) : 8 

مراجع

  1. "إزاحة المرجع" . MSDN . تم الاسترجاع في 19-09-2010 .
  2. "هل يؤدي استخدام &((struct name *)NULL -> b) إلى سلوك غير محدد في C11؟" . تم الاطلاع عليه بتاريخ 2015-02-07 .
  3. "إزاحة مرجع GCC" . مؤسسة البرمجيات الحرة . تم الاسترجاع في 19-09-2010 .
  4. "ما هو الغرض ونوع القيمة المُعادة من عامل التشغيل __builtin_offsetof؟" . تم الاطلاع عليه بتاريخ 20-10-2012 .
  5. غريغ كروه-هارتمان (يونيو 2003). "container_of()" . مجلة لينكس . تم الاطلاع عليه بتاريخ 19 سبتمبر 2010 .
  6. "البيانات والتصريحات في التعبيرات" . مؤسسة البرمجيات الحرة . تم الاسترجاع في 1 يناير 2016 .
  7. "offsetof reference" . cplusplus.com . تم الاسترجاع في 1 أبريل 2016 .
  8. "offsetof reference" . cppreference.com . تم الاطلاع عليه بتاريخ 2020-07-20 .
  9. ستيف جيسوب (يوليو 2009). "لماذا لا يمكنك استخدام دالة offsetof على هياكل البيانات غير POD في لغة C++؟" . موقع Stack Overflow . تم الاطلاع عليه بتاريخ 1 أبريل 2016 .