سؤال

أنا أتساءل عن الاستخدام العملي لـ #undef في لغة C.أنا أعمل من خلال K&R، وأنا على مستوى المعالج المسبق.معظم هذا كان عبارة عن مادة فهمتها (بشكل أو بآخر)، لكن شيئًا ما في الصفحة 90 (الطبعة الثانية) علق في ذهني:

قد تكون الأسماء غير محددة مع #undef، عادة لضمان أن الروتين هو في الحقيقة وظيفة ، وليس ماكرو:

#undef getchar

int getchar(void) { ... }

هل هذه ممارسة شائعة للدفاع ضد شخص ما؟ #define-ing ماكرو بنفس اسم وظيفتك؟أم أن هذا في الواقع نموذج لا يمكن أن يحدث في الواقع؟(على سبيل المثال، لا ينبغي لأي شخص في عقله الصحيح أو الخاطئ أو المجنون أن يعيد الكتابة getchar(), ، لذلك لا ينبغي أن تظهر.) مع أسماء الوظائف الخاصة بك، هل تشعر بالحاجة إلى القيام بذلك؟هل يتغير هذا إذا كنت تقوم بتطوير مكتبة ليستخدمها الآخرون؟

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

المحلول

ما تقوم به

إذا كنت تقرأ rel="nofollow وC مكتبة ستاندرد (1992)، سترى أن يسمح رأس <stdio.h> لتوفير getchar() وgetc() وحدات الماكرو تشبه وظيفة (بإذن خاص لgetc() لتقييم سيطة مؤشر ملفها أكثر من مرة!). ومع ذلك، حتى لو كان يوفر وحدات الماكرو، ملزمة بتنفيذ أيضا ليقوم بتقديم وظائف الفعلية التي نقوم بنفس العمل، في المقام الأول بحيث يمكنك الوصول إلى مؤشر دالة يسمى getchar() أو getc() وتمرير ذلك إلى وظائف أخرى.

وهذا هو، عن طريق القيام:

#include <stdio.h>
#undef getchar

extern int some_function(int (*)(void));

int core_function(void)
{
   int c = some_function(getchar);
   return(c);
}

وكما هو مكتوب، وcore_function() لا معنى له جدا، لكنه يوضح هذه النقطة. يمكنك أن تفعل الشيء نفسه مع وحدات الماكرو isxxxx() في <ctype.h> جدا، على سبيل المثال.

وعادة، لا تريد أن تفعل ذلك - كنت لا تريد عادة لإزالة تعريف الماكرو. ولكن، عندما كنت في حاجة إلى وظيفة حقيقية، يمكنك الحصول على عقد من ذلك. يمكن للناس الذين يقدمون المكتبات محاكاة وظائف المكتبة القياسية C إلى تأثير جيد.

حاجة نادرا

لاحظ أيضا أن واحدا من الأسباب التي نادرا ما تحتاج إلى استخدام #undef وضوحا هو أنه يمكنك استدعاء الدالة بدلا من الماكرو الكتابة:

int c = (getchar)();

ولأن الرمز المميز بعد getchar ليس (، فإنه ليس استدعاء الماكرو مثل وظيفة، لذلك يجب أن يكون إشارة إلى وظيفة. وبالمثل، فإن المثال الأول أعلاه، من شأنه تجميع وتشغيل بشكل صحيح حتى من دون #undef.

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

/* function.h */
…
extern int function(int c);
extern int other_function(int c, FILE *fp);
#define function(c) other_function(c, stdout);
…
/* function.c */

…

/* Provide function despite macro override */
int (function)(int c)
{
    return function(c, stdout);
}

والخط تعريف الدالة لا يحتج الماكرو لأن الرمز المميز بعد function ليس (. خط return لا استدعاء الماكرو.

نصائح أخرى

وغالبا ما تستخدم

وحدات الماكرو لتوليد الجزء الأكبر من التعليمات البرمجية. انها غالبا ما تكون الاستخدام الموضعي جدا، وأنه من الأسلم أن #undef أي وحدات الماكرو المساعد في نهاية رأس خاص من أجل تجنب الاشتباكات الاسم بحيث تم استيرادها الشفرة التي تم إنشاؤها الفعلي فقط في أي مكان آخر وحدات الماكرو استخدامها لتوليد رمز لا.

و/ تحرير: وكمثال على ذلك، لقد استعملت هذا لتوليد البنيات بالنسبة لي. وفيما يلي مقتطف من المشروع الفعلي:

#define MYLIB_MAKE_PC_PROVIDER(name) \
    struct PcApi##name { \
        many members …
    };

MYLIB_MAKE_PC_PROVIDER(SA)
MYLIB_MAKE_PC_PROVIDER(SSA)
MYLIB_MAKE_PC_PROVIDER(AF)

#undef MYLIB_MAKE_PC_PROVIDER

ولأن #defines المعالج كلها في مساحة اسم عالمي واحد، فإنه من السهل لصراعات مساحة لينتج، وخاصة عند استخدام طرف ثالث المكتبات. على سبيل المثال، إذا أردت إنشاء دالة اسمه OpenFile، قد لا تجميع بشكل صحيح، لأن <windows.h> ملف الرأس يحدد OpenFile رمز لتعيين إما OpenFileA أو OpenFileW (اعتمادا على إذا تم تعريف UNICODE أم لا). الحل الصحيح هو #undef OpenFile قبل تعريف الدالة.

وأنا فقط استخدامها عند ماكرو في ملف #included يتدخل مع واحدة من مهامي (على سبيل المثال، لديها نفس الاسم). ثم أنا #undef الماكرو حتى أتمكن من استخدام وظيفة بلدي.

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


int foo(int x, int y)
{
#define OUT_OF_RANGE(v, vlower, vupper) \
    if (v < vlower) {v = vlower; goto EXIT;} \
    else if (v > vupper) {v = vupper; goto EXIT;}

    /* do some calcs */
    x += (x + y)/2;
    OUT_OF_RANGE(x, 0, 100);
    y += (x - y)/2;
    OUT_OF_RANGE(y, -10, 50);

    /* do some more calcs and range checks*/
    ...

EXIT:
    /* undefine OUT_OF_RANGE, because we don't need it anymore */
#undef OUT_OF_RANGE
    ...
    return x;
}

لتظهر للقارئ أن هذا الماكرو هو مفيد فقط داخل الدالة غير معرفة أنه في نهاية المطاف. أنا لا أريد أن أشجع أي شخص استخدام مثل هذه وحدات الماكرو hackish. ولكن إذا كان لديك، #undef لهم في نهاية المطاف.

<اقتباس فقرة>   

هل هذه ممارسة شائعة للدفاع ضد شخص # تعريف جي ماكرو مع نفس اسم الدالة؟ أم أن هذا أكثر حقا من العينة التي لن تحدث في الواقع؟ (EG، لا أحد في حقه، الخطأ ولا مجنون العقل يجب إعادة كتابة getchar ()، لذلك لا ينبغي أن يأتي.)

وهناك القليل من الاثنين معا. سوف كود جيدة لا يتطلب استخدام #undef، ولكن هناك الكثير من التعليمات البرمجية سيئة هناك لديك للعمل مع. #undef يمكن لا تقدر بثمن عندما تسحب شخص خدعة مثل #define bool int.

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

  • ال assert يمكن تغيير تعريف الماكرو في منتصف وحدة الترجمة للحالة التي قد ترغب فيها في إجراء تصحيح الأخطاء على جزء من التعليمات البرمجية الخاصة بك دون غيرها.بالإضافة إلى assert نفسها تحتاج إلى أن تكون #undef'ed للقيام بذلك، و NDEBUG يحتاج الماكرو إلى إعادة تعريف لإعادة تكوين السلوك المطلوب assert

  • لقد رأيت تقنية تُستخدم للتأكد من تعريف العالميات مرة واحدة بالضبط باستخدام ماكرو للإعلان عن المتغيرات extern, ، ولكن سيتم إعادة تعريف الماكرو إلى لا شيء للحالة الفردية حيث يتم استخدام الرأس/التصريحات لتحديد المتغيرات.

شيء من هذا القبيل (أنا لا أقول أن هذا أسلوب جيد بالضرورة، فقط أسلوب رأيته في البرية):

/* globals.h */
/* ------------------------------------------------------ */
#undef GLOBAL
#ifdef DEFINE_GLOBALS
#define GLOBAL
#else
#define GLOBAL extern
#endif

GLOBAL int g_x;
GLOBAL char* g_name;
/* ------------------------------------------------------ */



/* globals.c */
/* ------------------------------------------------------ */
#include "some_master_header_that_happens_to_include_globals.h"

/* define the globals here (and only here) using globals.h */
#define DEFINE_GLOBALS
#include "globals.h"

/* ------------------------------------------------------ */

إذا كان من الممكن إلغاء تعريف الماكرو، فيجب أن يكون هناك وسيلة لإلغاء تعريفه.

يحدد متتبع الذاكرة الذي أستخدمه وحدات الماكرو الجديدة/المحذوفة الخاصة به لتتبع معلومات الملف/الخط.يكسر هذا الماكرو SC++L.

#pragma push_macro( "new" )
#undef new
#include <vector>
#pragma pop_macro( "new" )

فيما يتعلق بسؤالك الأكثر تحديدًا:غالبًا ما تتم محاكاة مساحات الأسماء في لغة C عن طريق بادئة وظائف المكتبة بمعرف.

سيؤدي إلغاء تحديد وحدات الماكرو بشكل أعمى إلى زيادة الارتباك وتقليل قابلية الصيانة وقد يؤدي إلى كسر الأشياء التي تعتمد على السلوك الأصلي.إذا كنت مجبرًا، استخدم الضغط/البوب ​​على الأقل للحفاظ على السلوك الأصلي في كل مكان آخر.

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