في C ++ ، هل تتبع وظائف variadic (تلك التي لديها ... في نهاية قائمة المعلمات) بالضرورة اتفاقية استدعاء __cdecl؟

StackOverflow https://stackoverflow.com/questions/2512746

سؤال

أعلم أن وظائف __stdcall لا يمكن أن تحتوي على علامات ، لكنني أريد أن أتأكد من عدم وجود منصات تدعم وظائف stdarg.h لاتصال المؤتمرات بخلاف __cdecl أو __stdcall.

هل كانت مفيدة؟

المحلول

يجب أن تكون اتفاقية الاتصال واحدة حيث يقوم المتصل بمسح الوسائط من المكدس (لأن Callee لا يعرف ما الذي سيتم تمريره).

هذا لا يتوافق بالضرورة مع ما تسميه Microsoft "__cdecl". على سبيل المثال فقط ، على SPARC ، ستمرير الوسيطات عادةً في السجلات ، لأن هذه هي الطريقة التي تم تصميم SPARC للعمل - تعمل بشكل أساسي كمكدس مكالمات يتم تسربها إلى الذاكرة الرئيسية إذا أصبحت المكالمات عميقة بما يكفي لذلك لن يتناسبوا مع التسجيل بعد الآن.

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

لماذا يفعل هذا الأمر لك؟ تتمثل الهدف من استخدام stdarg.h و Macros في إخفاء الاختلافات في استدعاء الاتفاقية من التعليمات البرمجية الخاصة بك ، بحيث يمكن أن تعمل مع وسيطات متغيرة بشكل محول.

تحرير ، استنادًا إلى التعليقات: حسنًا ، الآن أفهم ما تفعله (على الأقل بما يكفي لتحسين الإجابة). بالنظر إلى أن لديك بالفعل (على ما يبدو) رمز للتعامل مع الاختلافات في ABI الافتراضي ، فإن الأمور أبسط. هذا لا يترك سوى مسألة ما إذا كانت وظائف variadic تستخدم دائمًا "ABI الافتراضي" ، أيا كان ما يحدث للمنصة الموجودة. مع "stdcall" و "الافتراضي" باعتباره الخيارات الوحيدة ، أعتقد أن الإجابة على ذلك هي نعم. فقط على سبيل المثال ، على Windows ، wsprintf و wprintf كسر قاعدة الإبهام ، ويستخدم اتفاقية استدعاء CDECL بدلاً من stdcall.

نصائح أخرى

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

  • يجب أن يكون Callee قادرًا على الوصول إلى المعلمات التي ليست جزءًا من قائمة الوسيطة المتغيرة من إزاحة ثابتة من الجزء العلوي من المكدس. هذا يتطلب أن يدفع المترجم المعلمات إلى المكدس من اليمين إلى اليسار. (وهذا يشمل أشياء مثل المعلمة الأولى ل printf, مواصفات التنسيق. أيضًا ، يجب أيضًا اشتقاق عنوان قائمة الوسيطة المتغيرة نفسها من موقع معروف.)
  • يجب أن يكون المتصل مسؤولاً عن إزالة المعلمات من المكدس بمجرد إرجاع الوظيفة ، لأن المترجم فقط ، أثناء إنشاء الكود للمتصل ، يعرف عدد المعلمات التي تم دفعها على المكدس في المقام الأول. وظيفة variadic نفسها لا تحتوي على هذه المعلومات.

stdcall لن تعمل لأن Callee مسؤول عن تفجير المعلمات من المكدس. في أيام النوافذ القديمة التي يبلغ طولها 16 بت ، pascal لن تعمل لأنها دفعت المعلمات على المكدس من اليسار إلى اليمين.

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

النظر في الوظيفة التالية على نظام x86:

void __stdcall شيء (char *، ...) ؛

تعلن الوظيفة نفسها على أنها __stdcall ، وهي اتفاقية Callee-Clean. لكن لا يمكن أن تكون وظيفة المتغيرات callee-clean لأن Callee لا يعرف عدد المعلمات التي تم تمريرها ، لذلك لا تعرف عددها الذي يجب أن تنظف.

يقوم برنامج التحويل البرمجي Microsoft Visual Studio C/C ++ بحل هذا الصراع عن طريق تحويل اتفاقية الاتصال بصمت إلى __cdecl ، وهي اتفاقية الاتصال المتنوعة الوحيدة المدعومة للوظائف التي لا تأخذ هذه المعلمة مخفية.

لماذا يحدث هذا التحويل بصمت بدلاً من توليد تحذير أو خطأ؟

أظن أنه هو جعل خيارات التحويل البرمجي /GR (تعيين اتفاقية الاتصال الافتراضية على __fastcall) و /GZ (تعيين اتفاقية الاتصال الافتراضية إلى __stdcall) أقل إزعاجًا.

يعني التحويل التلقائي للوظائف المتنوعة إلى __cdecl أنه يمكنك فقط إضافة مفتاح سطر الأوامر /gr أو /GZ إلى خيارات المترجم الخاصة بك ، وسيظل كل شيء يعمل ويتم تشغيله (فقط مع اتفاقية الاتصال الجديدة).

هناك طريقة أخرى للنظر إلى هذا في عدم التفكير في المترجم تحويل variadic __stdcall إلى __cdecl ولكن بدلاً من مجرد قول "للوظائف المتنوعة ، __stdCall هو متصل."

انقر هنا

هل تقصد "المنصات التي تدعمها MSVC" أو كقاعدة عامة؟ حتى لو كنت تقصر نفسك على المنصات التي تدعمها MSVC ، فلا يزال لديك حالات مثل IA64 و AMD64 حيث لا يوجد سوى اتفاقية الاتصال "واحد" ، ويتم استدعاء اتفاقية الاتصال هذه __stdcall, ، لكنها بالتأكيد ليست هي نفسها __stdcall تحصل على x86.

AFAIK ، تنوع اتفاقيات الاتصال فريدة من نوعها لـ DOS/Windows على X86. تحتوي معظم المنصات الأخرى على مجموعة المترجمين مع نظام التشغيل وتوحيد الاتفاقية.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top