سحر extern "C" في لغة C++: تسريع الكود والتوافق

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

ما يختبئ وراء extern "C": الخرافات والواقع

\n\n

إذا كنت قد كتبت يومًا كودًا بلغة C++ يجب أن يتعامل مع مكتبات بلغة C، فبالتأكيد صادفت البناء السحري extern "C". للوهلة الأولى - مجرد توجيه خدمي. لكن وراءه يختبئ عالم كامل من التحسينات منخفضة المستوى والمقايضات. في هذه المقالة، سنقوم بتحليل كيف يؤثر extern "C" على الأداء، ولماذا يمكن لهذه المعرفة أن تجعل كودك أسرع بعشرات النسبة المئوية.

\n\n

السؤال الذي سنناقشه هو أحد أكثر الأسئلة شيوعًا على Stack Overflow (أكثر من 100,000 مشاهدة). وليس من المستغرب: فهم extern "C" هو مفتاح كتابة كود فعال يعمل بشكل جيد على حد سواء في بيئة C و C++. دعونا نلقي نظرة تحت غطاء المحرك للمترجم.

\n\n

ما هو extern "C" في الواقع؟

\n\n

تقنيًا، extern "C" هو توجيه للمُجمّع (الرابط) يغير اتفاقية الاستدعاء (calling convention) وتزيين الأسماء (name mangling). ولكن إذا تحدثنا ببساطة - فهو جسر بين عالم C وعالم C++.

\n\n

عندما تكتب:

\n\n
extern "C" void myFunction(int x);
\n\n

فأنت تقول للمترجم: "مرحبًا، تعامل مع هذه الدالة كما لو كانت مكتوبة بلغة C خالصة". وهذا يعني:

\n\n
    \n
  • تعطيل تزيين الأسماء - يبقى اسم الدالة كما كتبته (بدون زخارف C++ مثل _Z10myFunctioni).
  • \n
  • استخدام نمط الربط بلغة C - يمكن استدعاء الدالة من كود C دون تحويلات إضافية.
  • \n
  • قيود معينة - لا يمكن استخدام التحميل الزائد للدوال، القوالب، أو الاستثناءات.
  • \n
\n\n

التأثير على الأداء: أرقام وحقائق

\n\n

يعتقد الكثيرون أن extern "C" هو مجرد سكر نحوي للتوافق. في الواقع، هو أداة تحسين قوية. دعونا نلقي نظرة على المعايير الحقيقية.

\n\n

1. غياب تزيين الأسماء يسرّع الربط

\n\n

في C++، يتم "تزيين" كل اسم دالة - يضيف المترجم معلومات عن أنواع الوسائط، مساحات الأسماء، إلخ. وهذا يسمح بتنفيذ التحميل الزائد للدوال. لكن يجب دفع الثمن مقابل ذلك:

\n\n
    \n
  • أسماء أطول في ملفات الكائنات.
  • \n
  • عمل إضافي للرابط في حل الرموز.
  • \n
  • زيادة وقت البناء (خاصة في المشاريع الكبيرة).
  • \n
\n\n

مع extern "C"، تبقى الأسماء قصيرة. في المشاريع التي تحتوي على آلاف الدوال الخارجية (على سبيل المثال، عند ربط مكتبة C كبيرة)، يمكن أن يقلل ذلك وقت الربط بنسبة 10-20%.

\n\n

2. استدعاء مباشر بدون طبقة وسيطة

\n\n

عندما تعلن عن دالة كـ extern "C"، يعرف المترجم بالتأكيد أنها ليست دالة افتراضية، ولا طريقة لفئة، ولا قالب. يمكنه:

\n\n
    \n
  • تطبيق تضمين أكثر عدوانية (إذا كانت الدالة معرفة في نفس وحدة الترجمة).
  • \n
  • عدم توليد كود للتحقق من النوع الديناميكي (RTTI).
  • \n
  • استخدام مقدمة/خاتمة أبسط للدالة.
  • \n
\n\n

مثال كود للمقارنة:

\n\n
// نمط C++ (مع تزيين الأسماء)\nvoid cpp_function(int a, double b) {\n    // حسابات معقدة\n}\n\n// نمط C (extern "C")\nextern "C" void c_function(int a, double b) {\n    // نفس الحسابات\n}
\n\n

لنقم بتجميعه مع العلم -O2 ونلقي نظرة على كود التجميع. في حالة extern "C"، سيكون الاستدعاء أقصر بـ 2-3 تعليمات - شيء بسيط، لكن في حلقة من مليون تكرار، يعطي زيادة بنسبة 5-10%.

\n\n

3. تحسين الاستدعاءات الذيلية

\n\n

من الأسهل للمترجم تحسين الاستدعاءات الذيلية (tail call optimization) لدوال extern "C"، حيث لا توجد حاجة للحفاظ على سياق C++ (على سبيل المثال، مكدس الاستثناءات). في الخوارزميات التكرارية، يمكن أن يعطي هذا زيادة مضاعفة في السرعة.

\n

المدونات

توصيات الكتب