سؤال

دعونا نقول لدي وظيفة يقبل void (*)(void*) وظيفة مؤشر استخدام رد الاتصال:

void do_stuff(void (*callback_fp)(void*), void* callback_arg);

الآن, إذا كان لدي وظيفة من هذا القبيل:

void my_callback_function(struct my_struct* arg);

يمكنني القيام بذلك بأمان ؟

do_stuff((void (*)(void*)) &my_callback_function, NULL);

لقد بحثت في هذا السؤال و لقد بحثت في بعض ج المعايير التي تقول أنك يمكن أن يلقي إلى 'متوافق مؤشرات الدالة', ولكن أنا لا يمكن العثور على تعريف ما متوافق مع وظيفة مؤشر' يعني.

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

المحلول

بقدر C القياسية المعنية ، إذا كنت يلقي وظيفة مؤشر إلى مؤشر دالة من نوع مختلف ومن ثم استدعاء ذلك ، فمن السلوك غير معرف.انظر المرفق J. 2 (الإعلامية):

سلوك غير معرف في الحالات التالية:

  • مؤشر يستخدم استدعاء دالة التي نوع غير متوافق مع أشار إلى نوع (6.3.2.3).

القسم 6.3.2.3 الفقرة 8 على ما يلي:

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

لذلك وبعبارة أخرى ، يمكن أن يلقي مؤشر وظيفة إلى وظيفة مختلفة نوع المؤشر ، ويلقي عليه مرة أخرى و نسميها و الأشياء التي سوف تعمل.

تعريف متوافق هو معقد بعض الشيء.ويمكن العثور عليها في القسم 6.7.5.3 الفقرة 15:

لمدة سنتين وظيفة أنواع لتكون متوافقة ، سواء يحدد متوافق العودة أنواع127.

وعلاوة على ذلك, نوع المعلمة القوائم ، إذا كان كلاهما موجودا ، توافق في عدد من المعلمات في استخدام القطع منهي;المعلمات المقابلة يكون متوافق أنواع.إذا كان نوع واحد لديه المعلمة القائمة نوع و النوع الآخر هو محدد من قبل وظيفة declarator التي ليست جزءا من وظيفة التعريف الذي يحتوي على فارغ معرف قائمة, قائمة المعلمة لا يجوز القطع منهي ونوع من كل المعلمة يجب أن تكون متوافقة مع نوع ينتج عن تطبيق الافتراضي حجة الترقيات.إذا كان نوع واحد لديه المعلمة القائمة نوع و النوع الآخر هو المحدد من خلال وظيفة التعريف الذي يحتوي على (ربما فارغة) معرف قائمة ، سواء ا توافق في عدد من المعلمات ، ونوع من كل نموذج المعلمة يجب أن تكون متوافق مع نوع ينتج عن تطبيق الافتراضي الحجة الترقيات إلى نوع من المقابلة معرف.(في تحديد نوع التوافق من النوع المركب ، كل معلمة أعلن مع وظيفة أو مجموعة نوع يؤخذ بعد تعديل نوع و كل معلمة أعلن المؤهلة نوع هي وجود المشروط من نوع المعلنة.)

127) إذا كان كل وظيفة أنواع "الطراز القديم" ، أنواع المعلمة لا مقارنة.

قواعد تحديد ما إذا كان اثنين من أنواع متوافقة الموضحة في الجزء 6.2.7 و لن أقتبس منها هنا لأنها طويلة نوعا ما, لكن يمكنك أن تقرأ لهم على مشروع C99 القياسية (PDF).

القاعدة ذات الصلة هنا في القسم 6.7.5.1 الفقرة 2:

لمدة سنتين مؤشر أنواع لتكون متوافقة ، على حد سواء يجب أن يكون مطابق المؤهلين على حد سواء يجب أن تكون مؤشرات متوافقة أنواع.

ومن هنا منذ void* غير متوافق مع struct my_struct*, مؤشر دالة من نوع void (*)(void*) غير متوافق مع وظيفة مؤشر من نوع void (*)(struct my_struct*), لذا هذا الصب من مؤشرات دالة من الناحية الفنية السلوك غير معرف.

في الممارسة العملية ، على الرغم من يمكنك الحصول بأمان بعيدا مع صب مؤشرات دالة في بعض الحالات.في x86 الدعوة الاتفاقية الحجج يتم الضغط على المكدس ، وكل المؤشرات هي نفس الحجم (4 بايت في x86 أو 8 بايت في x86_64).استدعاء دالة المؤشر يتلخص في دفع الحجج على كومة به غير مباشرة الانتقال إلى مؤشر دالة الهدف, و من الواضح أن هناك فكرة من أنواع في الجهاز رمز المستوى.

الأشياء التي كنت بالتأكيد لا يمكن هل:

  • يلقي بين مؤشرات الدالة مختلف الاتفاقيات الدعوة.وسوف تصل الفوضى المكدس و في أحسن الأحوال, تحطم, في أسوأ الأحوال, النجاح بصمت مع ضخمة خطيئة ثغرة أمنية.في نظام التشغيل Windows برمجة, كنت كثيرا ما تمر مؤشرات الدالة حولها.Win32 تتوقع كل رد وظائف استخدام stdcall واصفا الاتفاقية (أي وحدات الماكرو CALLBACK, PASCAL, ، WINAPI جميع التوسع).إذا قمت بتمرير مؤشر دالة يستخدم معيار C الدعوة الاتفاقية (cdecl), السوء نتيجة.
  • في C++, ويلقي بين فئة مؤشرات دالة عضو العادية مؤشرات الدالة.هذا رحلات غالبا ما يصل C++ نوبي.فئة وظائف الأعضاء خفية this المعلمة إذا كنت يلقي عضو وظيفة إلى وظيفة منتظمة, لا يوجد this وجوه استخدام, ومرة أخرى, الكثير من سوء النتيجة.

آخر الفكرة السيئة التي قد تعمل في بعض الأحيان ولكن هو أيضا غير معروف السلوك:

  • الصب بين مؤشرات الدالة العادية المؤشرات (مثلا ، صب void (*)(void) إلى void*).مؤشرات الدالة ليست بالضرورة نفس حجم العادية المؤشرات منذ بعض أبنية أنها قد تحتوي على اضافية المعلومات السياقية.وربما هذا العمل موافق على x86, ولكن تذكر أنه من غير معرفة السلوك.

نصائح أخرى

سألت عن هذه القضية نفسها بالضبط فيما يتعلق ببعض الكود في Glib مؤخرا. (GLIB هي مكتبة أساسية لمشروع جنوم وكتبت في C.) قيل لي إطار Slots'N'N'Signals يعتمد عليه.

في جميع أنحاء التعليمات البرمجية، هناك العديد من مثيلات الصب من النوع (1) إلى (2):

  1. typedef int (*CompareFunc) (const void *a, const void *b)
  2. typedef int (*CompareDataFunc) (const void *b, const void *b, void *user_data)

من الشائع في السلسلة - من خلال المكالمات مثل هذا:

int stuff_equal (GStuff      *a,
                 GStuff      *b,
                 CompareFunc  compare_func)
{
    return stuff_equal_with_data(a, b, (CompareDataFunc) compare_func, NULL);
}

int stuff_equal_with_data (GStuff          *a,
                           GStuff          *b,
                           CompareDataFunc  compare_func,
                           void            *user_data)
{
    int result;
    /* do some work here */
    result = compare_func (data1, data2, user_data);
    return result;
}

نرى لنفسك هنا في g_array_sort(): http://git.gnome.org/browse/glib/tree/glib/garray.c.

الإجابات أعلاه مفصلة ومن المحتمل الصحيح - إذا أنت تجلس في لجنة المعايير. آدم وجوهان تستحق الائتمان لاستجاباتهم البئاسية. ومع ذلك، في البرية، ستجد هذا الرمز يعمل بشكل جيد. مثيرة للجدل؟ نعم. النظر في هذا: Clib Compliles / Works / اختبارات على عدد كبير من المنصات (Linux / Solaris / Windows / OS X) مع مجموعة واسعة من المحاصيل / الروابط / لوادر Kernel / Kernel (GCC / Clang / MSVC). المعايير ملعونين، أعتقد.

قضيت بعض الوقت في التفكير في هذه الإجابات. هنا هو استنتاجي:

  1. إذا كنت تكتب مكتبة ردودة، فقد يكون هذا موافقا. مفتاح التحذير - استخدم على مسؤوليتك الخاصة.
  2. آخر، لا تفعل ذلك.

التفكير أعمق بعد كتابة هذا الاستجابة، لن أتفاجأ إذا كان رمز الترجمة C يستخدم هذه الخدعة نفسها. ومنذ (معظم / كل شيء؟) مترجمات c الحديثة هي bootstrapped، وهذا يعني أن الحيلة آمنة.

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

النقطة حقا ليست حقا ما إذا كنت تستطيع. الحل التافه هو

void my_callback_function(struct my_struct* arg);
void my_callback_helper(void* pv)
{
    my_callback_function((struct my_struct*)pv);
}
do_stuff(&my_callback_helper);

لن يؤدي مترجم جيد إلى إنشاء رمز فقط ل my_callback_helper إذا كانت هناك حاجة حقا، وفي هذه الحالة ستكون سعيدا بذلك.

لديك نوع وظيفة متوافقة إذا كانت أنواع الإرجاع وأنواع المعلمات متوافقة - أساسا (الأمر أكثر تعقيدا في الواقع :)). التوافق هو نفسه "نفس النوع" فقط LAX للسماح له أنواع مختلفة ولكن لا يزال لديك شكل من أشكال القول "هذه الأنواع هي نفسها تقريبا". في C89، على سبيل المثال، كانت اثنين من الهياكل متوافقة إذا كانت متطابقة بطريقة أخرى ولكن اسمهم مختلف. يبدو أن C99 قد تغير ذلك. نقلا عن ج وثيقة الأساس (قراءة كاملة جدا، راجع للشغل!):

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

ومع ذلك - نعم هذا سلوك غير محدد بصرامة، لأن وظيفة Do_stuff الخاصة بك أو شخص آخر سيتصل بوظيفتك بمؤشر دالة void* كمعلمة، ولكن وظيفتك لها معلمة غير متوافقة. ولكن مع ذلك، أتوقع أن تجميع جميع المترجمات وتشغيلها دون أن يئن. ولكن يمكنك القيام بأنظف من خلال وجود وظيفة أخرى تأخذ void* (وتسجيل ذلك كدالة اتصال) التي ستسمى فقط لوظيفتك الفعلية بعد ذلك.

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

آمل أن أتمكن من جعلها أكثر وضوحا من خلال إظهار ما لن يعمل:

int my_number = 14;
do_stuff((void (*)(void*)) &my_callback_function, &my_number);
// my_callback_function will try to access int as struct my_struct
// and go nuts

أو...

void another_callback_function(struct my_struct* arg, int arg2) { something }
do_stuff((void (*)(void*)) &another_callback_function, NULL);
// another_callback_function will look for non-existing second argument
// on the stack and go nuts

في الأساس، يمكنك إلقاء المؤشرات على ما تريد، طالما استمرت البيانات في فرض معنى في وقت التشغيل.

إذا كنت تفكر في طريقة المكالمات وظيفة العمل في C/C++ ، ودفع بعض البنود على المكدس ، القفز إلى قانون جديد الموقع, تنفيذ, ثم البوب كومة على العودة.إذا كان لديك وظيفة مؤشرات وصف الوظائف مع عودة نفس النوع ونفس عدد/حجم من الحجج ، يجب أن تكون بخير.

وبالتالي أعتقد أنه يجب أن تكون قادرة على القيام بذلك بأمان.

مؤشرات الفراغ متوافقة مع أنواع أخرى من المؤشر. إنها العمود الفقري لكيفية وظائف MOLOC والأمي (memcpy, memcmp) الشغل. عادة، في C (بدلا من C ++) NULL هو ماكرو يعرف باسم ((void *)0).

انظر إلى 6.3.2.3 (البند 1) في C99:

قد يتم تحويل المؤشر إلى الفراغ إلى أو من مؤشر إلى أي نوع غير مكتمل أو كائن

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