مضمنة (لغة C و C++)

في لغتي البرمجة C و C++ ، الدالة المضمنة هي دالة مؤهلة بالكلمة المفتاحية ؛ وهذا يخدم غرضين:inline

  1. يُستخدم هذا كتوجيه للمُصرّف، حيث يُشير (ولكن لا يُلزم) إلى أن يقوم المُصرّف باستبدال جسم الدالة مباشرةً في الكود، وذلك عن طريق توسيع الكود مباشرةً في الكود، أي بإدراج رمز الدالة عند عنوان كل استدعاء للدالة، مما يُقلل من الحمل الزائد لاستدعاء الدالة. وهو في هذا الصدد يُشابه registerمُحدِّد فئة التخزين ، الذي يُقدّم بدوره تلميحًا لتحسين الأداء. [ 1 ]
  2. يتمثل الغرض الثاني في inlineتغيير سلوك الربط. وهذا ضروري بسبب نموذج الربط والتجميع المنفصل في لغتي C/C++، وتحديدًا لأن تعريف (جسم) الدالة يجب تكراره في جميع وحدات الترجمة التي تُستخدم فيها، للسماح بالتضمين أثناء التجميع ، الأمر الذي، إذا كانت للدالة روابط خارجية ، يتسبب في حدوث تعارض أثناء الربط (لأنه ينتهك تفرد الرموز الخارجية). تحل لغتا C وC++ (واللغات المشابهة مثل GNU C وVisual C++) هذه المشكلة بطرق مختلفة. [ 1 ]

مثال

inlineيمكن كتابة الدالة بلغة C أو C++ على النحو التالي :

دالة مضمنة void swap ( int * m , int * n ) { int tmp = * m ; * m = * n ; * n = tmp ; }

ثم، بيان مثل ما يلي:

تبديل ( & x , & y );

قد تُترجم إلى (إذا قرر المترجم إجراء التضمين، الأمر الذي يتطلب عادةً تمكين التحسين):

int tmp = x ; x = y ; y = tmp ;

عند تطبيق خوارزمية فرز تقوم بالكثير من عمليات التبديل، يمكن أن يؤدي ذلك إلى زيادة سرعة التنفيذ.

الدعم القياسي

تدعم لغتا C++ و C99 ، على عكس سابقتيهما K&R C و C89inline ، الدوال، وإن كان ذلك بدلالات مختلفة. في كلتا الحالتين، inlineلا يُجبر تضمين الدالة؛ إذ يُمكن للمُصرّف اختيار عدم تضمينها نهائيًا، أو تضمينها في حالات مُحددة فقط. تختلف المُصرّفات في قدرتها على تضمين الدوال المُعقدة. تدعم مُصرّفات C++ الشائعة، مثل Microsoft Visual C++ و GCC، خيارًا يسمح لها بتضمين أي دالة مُناسبة تلقائيًا، حتى تلك غير المُصنفة كدوال inline. مع ذلك، لا يُمكن ببساطة حذف inlineالكلمة المفتاحية للسماح للمُصرّف باتخاذ جميع قرارات التضمين، لأن الرابط سيُصدر حينها تحذيرًا بشأن التعريفات المُكررة في وحدات الترجمة المُختلفة. هذا لأن الكلمة المفتاحية inlineلا تُعطي المُصرّف تلميحًا فقط بضرورة تضمين الدالة، بل تُؤثر أيضًا على ما إذا كان المُصرّف سيُنشئ نسخة قابلة للاستدعاء من الدالة خارج التضمين (انظر فئات تخزين الدوال المُضمنة ).

ملحقات غير قياسية

يدعم برنامج GNU Cinline ، كجزء من لهجة gnu89 التي يوفرها، امتدادًا للغة C89. ومع ذلك، تختلف دلالاته عن دلالات كل من C++ وC99. كما يوفر armcc في وضع C90 inlineامتدادًا غير قياسي، بدلالات مختلفة عن gnu89 وC99.

توفر بعض التطبيقات وسيلة لإجبار المترجم على تضمين دالة، عادةً عن طريق محددات الإعلان الخاصة بالتطبيق:

  • مايكروسوفت فيجوال سي++:__forceinline
  • gcc أو clang : __attribute__((always_inline))أو __attribute__((__always_inline__))، والأخير مفيد لتجنب التعارض مع ماكرو معرف من قبل المستخدم باسم always_inline.

قد يؤدي الاستخدام العشوائي لهذه الخاصية إلى زيادة حجم الكود (ملف تنفيذي ضخم)، وتحسن طفيف أو معدوم في الأداء، بل وفي بعض الحالات إلى تراجع الأداء. علاوة على ذلك، لا يستطيع المُصرّف تضمين الدالة في جميع الظروف، حتى عند فرض التضمين؛ في هذه الحالة، يُصدر كل من gcc وVisual C++ تحذيرات.

يُعدّ فرض تضمين المحتوى مفيدًا في الحالات التالية:

  • inlineلا يحترمها المترجم (يتجاهلها محلل التكلفة/الفائدة الخاص بالمترجم)
  • يُعد تضمين النتائج ضروريًا لتعزيز الأداء

لضمان قابلية نقل التعليمات البرمجية، يمكن استخدام توجيهات المعالج المسبق التالية:

#ifdef _MSC_VER #define forceinline __forceinline #elif defined(__GNUC__) #define forceinline inline __attribute__((__always_inline__)) #elif defined(__CLANG__) #if __has_attribute(__always_inline__) #define forceinline inline __attribute__((__always_inline__)) #else #define forceinline inline #endif #else #define forceinline inline #endif

فئات تخزين الدوال المضمنة

static inlineله نفس التأثيرات في جميع لهجات لغة C ولغة C++. وسيصدر نسخة مرئية محليًا (خارج نطاق الدالة) إذا لزم الأمر.

بغض النظر عن فئة التخزين، يمكن للمترجم تجاهل inlineالمؤهل وإنشاء استدعاء دالة في جميع لهجات لغة C ولغة C++.

يختلف تأثير فئة التخزين externعند تطبيقها أو عدم تطبيقها على inlineالدوال بين لهجات لغة C [ 2 ] ولغة C++. [ 3 ]

C99

في لغة C99، لا تُصدر الدالة المُعرَّفة inlineأي دالة مرئية خارجيًا، بينما تُصدر الدالة المُعرَّفة extern inlineدائمًا دالة مرئية خارجيًا. على عكس لغة C++، لا توجد طريقة لطلب إصدار دالة مرئية خارجيًا مشتركة بين وحدات الترجمة فقط عند الحاجة.

إذا inlineتم خلط التصريحات مع extern inlineالتصريحات أو مع التصريحات غير المؤهلة (أي بدون inlineمؤهل أو فئة تخزين)، فيجب أن تحتوي وحدة الترجمة على تعريف (بغض النظر عما إذا كان غير مؤهل أو مؤهل inlineأو غير مؤهل extern inline) وسيتم إصدار دالة مرئية خارجيًا له.

يتطلب تعريف دالة inlineوجود دالة أخرى تحمل نفس الاسم في مكان آخر من البرنامج، سواء كانت مُعرَّفة extern inlineأو بدون مُحدِّد. إذا وُجد أكثر من تعريف واحد لهذه الدالة في البرنامج، فسيُصدر الرابط تحذيرًا بشأن الرموز المُكرَّرة. أما إذا لم تكن الدالة موجودة، فلن يُصدر الرابط تحذيرًا بالضرورة، لأنه إذا أمكن تضمين جميع استخداماتها، فلن تكون هناك حاجة إليها. ولكن قد يُصدر تحذيرًا، لأن المُصرِّف يستطيع دائمًا تجاهل المُحدِّد inlineوإنشاء استدعاءات للدالة بدلًا منه، كما يحدث عادةً عند تجميع الكود بدون تحسين. (قد يكون هذا هو السلوك المطلوب، إذا كان من المفترض تضمين الدالة في كل مكان، ويجب توليد خطأ إذا لم يتم ذلك). تتمثل إحدى الطرق المُلائمة في تعريف الدوال inlineفي ملفات رأسية وإنشاء ملف .c واحد لكل دالة، يحتوي على extern inlineتعريف لها ويتضمن ملف الرأس الخاص بها. لا يهم ما إذا كان التعريف قبل أو بعد التضمين.

لتجنب إضافة تعليمات برمجية غير قابلة للوصول إلى الملف التنفيذي النهائي في حال تضمين جميع استخدامات دالة ما، يُنصح [ 3 ] بوضع ملفات الكائنات لجميع ملفات .c التي تحتوي على دالة واحدة في ملف extern inlineمكتبة ثابتة ، عادةً باستخدام ` .c ...ar rcsinlineinlinesar rcs

يُعدّ استخدام تحسين وقت الربط بدلاً من المكتبة حلاً بديلاً . يوفر gcc خيارًا -Wl,--gc-sectionsلحذف الأقسام التي تحتوي على جميع الدوال غير المستخدمة. ينطبق هذا على ملفات الكائنات التي تحتوي على شيفرة extern inlineدالة واحدة غير مستخدمة. مع ذلك، فإنه يحذف أيضًا جميع الأقسام غير المستخدمة الأخرى من جميع ملفات الكائنات الأخرى، وليس فقط تلك المتعلقة extern inlineبالدوال غير المستخدمة. (قد يكون من المرغوب فيه ربط الدوال في الملف التنفيذي التي سيستدعيها المبرمج من المصحح بدلاً من البرنامج نفسه، على سبيل المثال، لفحص الحالة الداخلية للبرنامج). باستخدام هذا الأسلوب، يُمكن أيضًا استخدام ملف .c واحد يحتوي على جميع extern inlineالدوال بدلاً من ملف .c لكل دالة. عندئذٍ، يجب تجميع الملف باستخدام -fdata-sections -ffunction-sections. مع ذلك، تحذر صفحة دليل gcc من ذلك، قائلةً: "استخدم هذه الخيارات فقط عندما تكون هناك فوائد كبيرة من القيام بذلك".

يوصي البعض بنهج مختلف تمامًا، وهو تعريف الدوال كـ `<div>` static inlineبدلاً من تعريفها inlineفي ملفات الرأس. [ 2 ] عندئذٍ، لن يتم توليد أي كود غير قابل للوصول. مع ذلك، لهذا النهج عيب في الحالة المعاكسة: سيتم توليد كود مكرر إذا تعذر تضمين الدالة في أكثر من وحدة ترجمة واحدة. لا يمكن مشاركة كود الدالة الناتج بين وحدات الترجمة لأنه يجب أن يكون له عناوين مختلفة. هذا عيب آخر؛ فأخذ عنوان دالة كهذه، مُعرَّفة كـ `<div>` static inlineفي ملف رأس، سيعطي قيمًا مختلفة في وحدات الترجمة المختلفة. لذلك، static inlineيجب استخدام الدوال فقط إذا كانت تُستخدم في وحدة ترجمة واحدة فقط، مما يعني أنه يجب أن تُدرج فقط في ملف .c الخاص بها، وليس في ملف رأس.

gnu89

تُعدّ دلالات gnu89 للدالة ` inlineand` extern inlineمعاكسة تمامًا لدلالات C99، [ 4 ] باستثناء أن gnu89 تسمح بإعادة تعريف دالة extern inlineكدالة غير مُؤهّلة، بينما inlineلا تسمح C99 بذلك. [ 5 ] وبالتالي، فإن gnu89 extern inlineبدون إعادة تعريف تُشبه C99 inline، و gnu89 inlineتُشبه C99 extern inline؛ بعبارة أخرى، في gnu89، ستُصدر الدالة المُعرّفة inlineدائمًا `and`، extern inlineولن تُصدر الدالة المُعرّفة أبدًا `and` دالة مرئية خارجيًا. والسبب في ذلك هو أنها تُطابق المتغيرات، التي لن يتم حجز مساحة تخزين لها أبدًا إذا تم تعريفها كـ ` externand`، وستُحجز دائمًا إذا تم تعريفها بدون `and`. أما السبب في C99، على النقيض من ذلك، فهو أنه سيكون من المُثير للدهشة أن يكون لاستخدام inline`and` تأثير جانبي - وهو إصدار نسخة غير مُضمّنة من الدالة دائمًا - وهو ما يُخالف ما يُشير إليه اسمها.

تنطبق الملاحظات الواردة في C99 حول الحاجة إلى توفير مثيل دالة مرئي خارجيًا واحد بالضبط للدوال المضمنة وحول المشكلة الناتجة عن التعليمات البرمجية التي لا يمكن الوصول إليها على gnu89 أيضًا.

استخدم برنامج gcc حتى الإصدار 4.2 inlineدلالات gnu89 حتى عند -std=c99تحديدها صراحةً. [ 6 ] مع الإصدار 5، [ 5 ] انتقل gcc من gnu89 إلى لهجة gnu11، مما فعّل inlineدلالات C99 افتراضيًا. لاستخدام دلالات gnu89، يجب تفعيلها صراحةً، إما باستخدام -std=gnu89أو ، لتفعيلها فقط عند تضمينها، -fgnu89-inlineأو بإضافة gnu_inlineالسمة إلى جميع inlineالتصريحات. لضمان دلالات C99، يمكن استخدام -std=c99، أو -std=c11، -std=gnu99أو -std=gnu11(بدون ). [ 3 ]-fgnu89-inline

لغة سي++

في لغة C++، تقوم الدالة المُعرَّفة inline، عند الحاجة، بإنشاء دالة مشتركة بين وحدات الترجمة، وذلك عادةً بوضعها في القسم المشترك من ملف الكائن الذي تتطلبه. يجب أن يكون تعريف الدالة متطابقًا في كل مكان، مع inlineاستخدام المُحدِّد `. في C++، extern inlineيُعادل `` inline`. يكمن الأساس المنطقي لنهج C++ في كونه الطريقة الأنسب للمبرمج، إذ لا يتطلب اتخاذ أي احتياطات خاصة لإزالة التعليمات البرمجية غير القابلة للوصول، وكما هو الحال مع الدوال العادية، لا فرق في externتحديد ``` أم لا.

تتم إضافة المؤهل inlineتلقائيًا إلى دالة معرفة كجزء من تعريف فئة.

أرمك

يوفر armcc في وضع C90 extern inlineدلالات inlineمماثلة لتلك الموجودة في C++: ستُصدر هذه التعريفات دالةً مشتركة بين وحدات الترجمة عند الحاجة. في وضع C99، extern inlineيُصدر armcc دائمًا دالةً، ولكن كما هو الحال في C++، ستكون هذه الدالة مشتركة بين وحدات الترجمة. وبالتالي، يمكن تعريف الدالة نفسها extern inlineفي وحدات ترجمة مختلفة. [ 7 ] يتوافق هذا مع السلوك التقليدي لمترجمات لغة C في أنظمة Unix [ 8 ] عند externتعريف متغيرات عامة غير مهيأة.

قيود

يتطلب الحصول على عنوان دالة inlineما وجود رمز لإصدار نسخة غير مضمنة من تلك الدالة في جميع الأحوال.

في لغة C99، لا يجوز لدالة inline`or` الوصول إلى المتغيرات العامة أو تعريف متغيرات غير محلية. قد تكون المتغيرات المحلية كائنات مختلفة في وحدات الترجمة المختلفة، أو قد لا تكون كذلك، وذلك بحسب ما إذا كانت الدالة مضمنة أو تم استدعاؤها. يُسمح فقط للتعريفات بالإشارة إلى المعرفات ذات الارتباط الداخلي دون قيود؛ وستكون هذه المعرفات كائنات مختلفة في كل وحدة ترجمة. في لغة C++، يُسمح بكل من المتغيرات المحلية وغير المحلية، وتشير إلى الكائن نفسه في جميع وحدات الترجمة.extern inlinestaticconststaticconst staticstatic inlineconstconststatic

لا يمكن لـ GCC تضمين الدوال إذا [ 3 ]

  1. إنها متغيرة ،
  2. يستخدم alloca،
  3. استخدم المحسوب goto،
  4. استخدم غير محلي goto،
  5. استخدم الدوال المتداخلة ،
  6. يستخدم setjmp،
  7. يستخدم __builtin_longjmp،
  8. استخدم __builtin_return، أو
  9. يستخدم __builtin_apply_args.

استنادًا إلى مواصفات مايكروسوفت على موقع MSDN، لا يمكن لـ MS Visual C++ تضمين العناصر (ولا حتى باستخدام __forceinline)، إذا

  1. يتم تجميع الدالة أو مستدعيها باستخدام /Ob0 (الخيار الافتراضي لعمليات بناء التصحيح).
  2. تستخدم الدالة والمستدعي أنواعًا مختلفة من معالجة الاستثناءات (معالجة استثناءات C++ في أحدهما، ومعالجة الاستثناءات المهيكلة في الآخر).
  3. تحتوي الدالة على قائمة وسائط متغيرة .
  4. تستخدم الدالة التجميع المضمن ، ما لم يتم تجميعها باستخدام /Og أو /Ox أو /O1 أو /O2.
  5. الدالة تكرارية ولا تُرفق بـ #pragma inline_recursion(on). باستخدام التوجيه البرمجي، تُضمّن الدوال التكرارية إلى عمق افتراضي يبلغ 16 استدعاءً. لتقليل عمق التضمين، استخدم inline_depthالتوجيه البرمجي.
  6. الدالة افتراضية ويتم استدعاؤها افتراضياً. يمكن تضمين الاستدعاءات المباشرة للدوال الافتراضية.
  7. يأخذ البرنامج عنوان الدالة، ويتم استدعاء الدالة عبر المؤشر المؤدي إليها. ويمكن تضمين الاستدعاءات المباشرة للدوال التي تم الحصول على عناوينها.
  8. كما تم تمييز الدالة بالمعدِّل العاري __declspec.

مشاكل

إلى جانب المشاكل المتعلقة بالتوسيع المضمن بشكل عام (انظر التوسيع المضمن §  التأثير على الأداءinlineقد لا تكون الدوال كميزة لغوية ذات قيمة كما تبدو، وذلك لعدد من الأسباب:

  • في كثير من الأحيان، يكون المترجم في وضع أفضل من الإنسان لتحديد ما إذا كان ينبغي تضمين دالة معينة. أحيانًا قد لا يتمكن المترجم من تضمين جميع الدوال التي يحددها المبرمج.
  • من النقاط المهمة التي يجب ملاحظتها أن الكود (للدالة inline) يتم عرضه لعميله (الدالة المستدعِية).
  • مع تطور الدوال، قد تصبح مناسبة للتضمين المباشر في مواضع لم تكن مناسبة لها سابقًا، أو قد لا تكون مناسبة للتضمين المباشر في مواضع كانت مناسبة لها سابقًا. ورغم أن تضمين الدالة أو إلغاء تضمينها أسهل من تحويلها من وإلى وحدات الماكرو، إلا أنه لا يزال يتطلب صيانة إضافية لا تُحقق عادةً فائدة تُذكر.
  • يمكن أن تؤدي الدوال المضمنة المستخدمة في التكاثر في أنظمة التجميع الأصلية القائمة على لغة C إلى زيادة وقت التجميع، نظرًا لنسخ التمثيل الوسيط لأجسامها في كل موقع استدعاء.
  • يتطلب معيار inlineC99 تعريفًا خارجيًا واحدًا فقط للدالة، في حال استخدامها في أي مكان. إذا لم يُقدّم المبرمج هذا التعريف، فقد يؤدي ذلك بسهولة إلى أخطاء في الرابط. قد يحدث هذا عند تعطيل التحسين، الذي يمنع عادةً تضمين الدوال. من ناحية أخرى، قد يؤدي إضافة التعريفات إلى عدم إمكانية الوصول إلى التعليمات البرمجية إذا لم يتجنب المبرمج ذلك بعناية، وذلك بوضعها في مكتبة للربط، أو باستخدام تحسين وقت الربط، أو غير ذلك static inline.
  • في لغة C++، من الضروري تعريف inlineالدالة في كل وحدة (وحدة ترجمة) تستخدمها، بينما يجب تعريف الدالة العادية في وحدة واحدة فقط. وإلا فلن يكون من الممكن تجميع وحدة واحدة بشكل مستقل عن باقي الوحدات. وبحسب المُجمِّع، قد يؤدي هذا إلى احتواء كل ملف كائن على نسخة من شيفرة الدالة، وذلك لكل وحدة تستخدمها ولا يمكن تضمينها مباشرةً في الشيفرة الأصلية.
  • في البرمجيات المدمجة ، غالبًا ما تتطلب بعض الدوال وضعها في أقسام محددة من التعليمات البرمجية باستخدام تعليمات خاصة للمترجم، مثل عبارات "pragma". أحيانًا، قد تحتاج دالة في قسم ذاكرة إلى استدعاء دالة أخرى في قسم ذاكرة آخر، وإذا تم تضمين الدالة المستدعاة، فقد ينتهي بها المطاف في قسم غير مناسب. على سبيل المثال، قد تكون مساحة التعليمات البرمجية في أقسام الذاكرة عالية الأداء محدودة للغاية، وإذا استدعت دالة تنتمي إلى هذه المساحة دالة كبيرة أخرى غير مخصصة لقسم الذاكرة عالي الأداء، وتم تضمين الدالة المستدعاة بشكل غير صحيح، فقد يؤدي ذلك إلى نفاد مساحة التعليمات البرمجية في قسم الذاكرة عالي الأداء. لهذا السبب، من الضروري أحيانًا التأكد من عدم تضمين الدوال.

يقتبس

يُعرّف تعريف الدالة  ... باستخدام مُحدِّد inline دالة مضمنة. يُشير مُحدِّد inline إلى أن استبدال جسم الدالة مباشرةً عند نقطة الاستدعاء يُفضَّل على آلية استدعاء الدالة المعتادة. لا يُشترط على التنفيذ إجراء هذا الاستبدال عند نقطة الاستدعاء؛ ومع ذلك، حتى في حال حذفه، يجب مراعاة القواعد الأخرى للدوال المضمنة المُحدَّدة في 7.1.2.

ISO/IEC 14882:2011، معيار C++ الحالي، القسم 7.1.2

الدالة المُعلنة باستخدام مُحدد الدالة المضمنة هي دالة مضمنة  ... إن جعل الدالة دالة مضمنة يُشير إلى ضرورة أن تكون استدعاءات الدالة بأسرع ما يُمكن. مدى فعالية هذه الاقتراحات يعتمد على التنفيذ ( ملاحظة: على سبيل المثال، قد لا يُجري التنفيذ استبدالًا مضمنًا أبدًا، أو قد يُجري استبدالًا مضمنًا فقط للاستدعاءات ضمن نطاق إعلان مضمن ) .

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

ISO 9899:1999(E)، معيار C99، القسم 6.7.4

انظر أيضاً

مراجع