#ifdef vs #if - أيهما أفضل/أكثر أمانًا كوسيلة لتمكين/تعطيل تجميع أقسام معينة من التعليمات البرمجية؟

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

سؤال

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

في الأساس، لدينا بعض عبارات تصحيح الأخطاء التي نقوم بإيقاف تشغيلها أثناء التطوير العادي.أنا شخصياً أفضل القيام بما يلي:

//---- SomeSourceFile.cpp ----

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

#if(DEBUG_ENABLED)
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

على الرغم من ذلك، يفضل بعض الفريق ما يلي:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

#ifdef DEBUG_ENABLED
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

...أي من هذه الطرق تبدو أفضل بالنسبة لك ولماذا؟شعوري هو أن الأول أكثر أمانًا لأنه يوجد دائمًا شيء محدد ولا يوجد خطر من أن يؤدي إلى تدمير تعريفات أخرى في مكان آخر.

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

المحلول

كان رد فعلي الأولي #ifdef, ، بالطبع, ، لكنني أعتقد #if في الواقع بعض المزايا المهمة لهذا - وإليك السبب:

أولا، يمكنك استخدام DEBUG_ENABLED في المعالج المسبق و الاختبارات المجمعة.مثال - في كثير من الأحيان، أريد مهلات أطول عند تمكين التصحيح، لذلك استخدم #if, ، أستطيع أن أكتب هذا

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

...بدلاً من ...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

ثانيًا، أنت في وضع أفضل إذا كنت تريد الهجرة من بلد ما #define إلى ثابت عالمي. #defineعادة ما يتم رفض s من قبل معظم مبرمجي C++.

وثالثًا، تقول إن لديك انقسامًا في فريقك.أعتقد أن هذا يعني أن الأعضاء المختلفين قد تبنوا بالفعل أساليب مختلفة، وأنك بحاجة إلى توحيد المعايير.حكم ذلك #if هو الخيار المفضل يعني أن استخدام التعليمات البرمجية #ifdef سيتم تجميع - وتشغيل - حتى عندما DEBUG_ENABLED هو زائف.وهو كذلك كثيراً من الأسهل تعقب وإزالة مخرجات التصحيح التي يتم إنتاجها عندما لا يكون من المفترض أن تكون كذلك، وليس العكس.

أوه، ونقطة بسيطة في سهولة القراءة.يجب أن تكون قادرًا على استخدام صحيح/خطأ بدلاً من 0/1 في ملفك #define, ، ولأن القيمة عبارة عن رمز معجمي واحد، فهي المرة الوحيدة التي لا تحتاج فيها إلى أقواس حولها.

#define DEBUG_ENABLED true

بدلاً من

#define DEBUG_ENABLED (1)

نصائح أخرى

كلاهما بشع.بدلاً من ذلك، قم بما يلي:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

ثم عندما تحتاج إلى رمز التصحيح، ضعه في الداخل D();.وبرنامجك غير ملوث بمتاهات بشعة #ifdef.

#ifdef يتحقق فقط مما إذا تم تعريف الرمز المميز أم لا

#define FOO 0

ثم

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"

لقد واجهنا نفس المشكلة عبر ملفات متعددة، وهناك دائمًا مشكلة تتمثل في نسيان الأشخاص تضمين ملف "علامة الميزات" (مع قاعدة تعليمات برمجية تزيد عن 41000 ملف، يكون من السهل القيام بذلك).

إذا كان لديك ميزة.ح:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

ولكن بعد ذلك نسيت تضمين ملف الرأس في file.cpp:

#if COOL_FEATURE
    // definitely awesome stuff here...
#endif

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

لقد اعتمدنا طريقة محمولة لتصحيح هذه الحالة بالإضافة إلى اختبار حالة الميزة:وحدات الماكرو الوظيفية.

إذا قمت بتغيير الميزة المذكورة أعلاه إلى:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE() 1

#endif // FEATURE_H

ولكن بعد ذلك نسيت مرة أخرى تضمين ملف الرأس في file.cpp:

#if COOL_FEATURE()
    // definitely awseome stuff here...
#endif

قد يكون هناك خطأ في المعالج المسبق بسبب استخدام ماكرو دالة غير محددة.

لأغراض تنفيذ الترجمة الشرطية، #if و #ifdef هما بالكاد نفس الشيء، ولكن ليس تماما.إذا كان تجميعك الشرطي يعتمد على رمزين، فلن يعمل #ifdef أيضًا.على سبيل المثال، لنفترض أن لديك رمزي الترجمة الشرطية، PRO_VERSION وTRIAL_VERSION، فقد يكون لديك شيء مثل هذا:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

استخدام #ifdef ما ورد أعلاه يصبح أكثر تعقيدًا، خاصة تشغيل الجزء #else.

أنا أعمل على كود يستخدم الترجمة الشرطية على نطاق واسع ولدينا مزيج من #if و #ifdef.نحن نميل إلى استخدام #ifdef/#ifndef للحالة البسيطة و#if عندما يتم تقييم رمزين أو أكثر.

أعتقد أن الأمر يتعلق تمامًا بالأسلوب.لا يتمتع أي منهما بميزة واضحة على الآخر.

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

أنا نفسي أفضّل:

#if defined(DEBUG_ENABLED)

نظرًا لأنه يجعل من السهل إنشاء تعليمات برمجية تبحث عن الحالة المعاكسة، فمن الأسهل اكتشافها:

#if !defined(DEBUG_ENABLED)

ضد.

#ifndef(DEBUG_ENABLED)

إنها مسألة أسلوب.لكنني أوصي بطريقة أكثر إيجازًا للقيام بذلك:

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif

debug_print("i=%d\n", i);

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

إذا تلقيت التحذير "التعبير ليس له أي تأثير" وأردت التخلص منه، فإليك البديل:

void dummy(const char*, ...)
{}

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif

debug_print("i=%d\n", i);

#if يمنحك خيار ضبطه على 0 لإيقاف تشغيل الوظيفة، مع الاستمرار في اكتشاف وجود المفتاح.
أنا شخصياً دائماً #define DEBUG 1 حتى أتمكن من التقاطها باستخدام #if أو #ifdef

#إذا و#تعريف MY_MACRO (0)

استخدام #if يعني أنك قمت بإنشاء ماكرو "تعريف"، أي شيء سيتم البحث عنه في الكود ليتم استبداله بـ "(0)".هذا هو "الجحيم الكلي" الذي أكره رؤيته في C++، لأنه يلوث الكود بتعديلات الكود المحتملة.

على سبيل المثال:

#define MY_MACRO (0)

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

يعطي الخطأ التالي على g++:

main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|

فقط واحد خطأ.

مما يعني أن الماكرو الخاص بك قد تفاعل بنجاح مع كود C++ الخاص بك:تم استدعاء الوظيفة بنجاح.في هذه الحالة البسيطة، الأمر مسلي.لكن تجربتي الخاصة مع تشغيل وحدات الماكرو بصمت مع الكود الخاص بي ليست مليئة بالبهجة والوفاء، لذا...

#ifdef و #define MY_MACRO

استخدام #ifdef يعني أنك "تحدد" شيئًا ما.ليس أنك تعطيه قيمة.لا يزال ملوثًا، ولكن على الأقل، سيتم "استبداله بلا شيء"، ولن يتم رؤيته بواسطة كود C++ كبيان كود شرعي.نفس الكود أعلاه، مع تعريف بسيط، هو:

#define MY_MACRO

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

يعطي التحذيرات التالية:

main.cpp||In function ‘int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function ‘int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|

لذا...

خاتمة

أفضل العيش بدون وحدات ماكرو في الكود الخاص بي، ولكن لأسباب متعددة (تحديد حراس الرأس، أو تصحيح وحدات الماكرو)، لا أستطيع ذلك.

لكن على الأقل، أود أن أجعلها أقل تفاعلية قدر الإمكان باستخدام كود C++ الشرعي الخاص بي.مما يعني استخدام #define بدون قيمة، واستخدام #ifdef و #ifndef (أو حتى #if كما اقترح جيم باك)، والأهم من ذلك كله، منحهم أسماء طويلة جدًا وغريبة جدًا بحيث لا يستخدمها أي شخص بكامل قواه العقلية. إنه "عن طريق الصدفة"، ولن يؤثر بأي حال من الأحوال على كود C++ الشرعي.

ما بعد النص

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

#define MY_MACRO @@@@@@@@@@@@@@@@@@

يمكن استخدامها مع #ifdef و #ifndef، ولكن لا تسمح بتجميع التعليمات البرمجية إذا تم استخدامها داخل دالة...لقد حاولت ذلك بنجاح على g++، وأظهر الخطأ:

main.cpp|410|error: stray ‘@’ in program|

مثير للاهتمام.:-)

الأول يبدو أكثر وضوحا بالنسبة لي.يبدو الأمر أكثر طبيعية جعله علامة مقارنة بـ محدد/غير محدد.

كلاهما متساويان تمامًا.في الاستخدام الاصطلاحي، يتم استخدام #ifdef فقط للتحقق من التعريف (وما سأستخدمه في مثالك)، في حين يتم استخدام #if في تعبيرات أكثر تعقيدًا، مثل #if محدد(A) && !محدد(B).

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

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

#ifdef macro

تعني "إذا تم تعريف الماكرو" أو "إذا كان الماكرو موجودًا".قيمة الماكرو لا يهم هنا.يمكن أن يكون أيا كان.

#if macro

إذا قارنت دائمًا بقيمة.في المثال أعلاه هي المقارنة الضمنية القياسية:

#if macro !=0

مثال لاستخدام #if

#if CFLAG_EDITION == 0
    return EDITION_FREE;
#elif CFLAG_EDITION == 1
    return EDITION_BASIC;
#else
    return EDITION_PRO;
#endif

يمكنك الآن إما وضع تعريف CFLAG_EDITION إما في الكود الخاص بك

#define CFLAG_EDITION 1 

أو يمكنك تعيين الماكرو كعلامة مترجم.أيضًا انظر هنا.

اعتدت على استخدام #ifdef, ، ولكن عندما قمت بالتبديل إلى Doxygen للتوثيق، وجدت أنه لا يمكن توثيق وحدات الماكرو التي تم التعليق عليها (أو على الأقل، يصدر Doxygen تحذيرًا).وهذا يعني أنه لا يمكنني توثيق وحدات ماكرو تبديل الميزات غير الممكّنة حاليًا.

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

لذلك، في هذه الحالة، من الأفضل دائمًا تحديد وحدات الماكرو واستخدامها #if.

لقد استخدمت دائمًا علامات #ifdef والمترجم لتعريفها ...

وبدلاً من ذلك، يمكنك الإعلان عن ثابت عام واستخدام C++ if بدلاً من المعالج المسبق #if.يجب على المترجم أن يقوم بتحسين الفروع غير المستخدمة لك، وسوف يكون الكود الخاص بك أكثر نظافة.

هنا هو ما C++ مسكتك بواسطة ستيفن سي.يقول Dewhurst عن استخدام #if's.

هناك اختلاف في حالة اختلاف طريقة تحديد التعريف الشرطي للسائق:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

انتاج:

344c344
< #define A 
---
> #define A 1

هذا يعني ذاك -DA هو مرادف ل -DA=1 وإذا تم حذف القيمة، فقد يؤدي ذلك إلى مشاكل في حالة #if A الاستخدام.

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