لماذا استخدم على ما يبدو لا معنى له افعل حين و إذا كان آخر البيانات في وحدات الماكرو ؟

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

  •  03-07-2019
  •  | 
  •  

سؤال

في العديد من C/C++ وحدات الماكرو أنا أرى رمز ماكرو ملفوفة في ما يبدو وكأنه لا معنى له do while حلقة.وإليك أمثلة على ذلك.

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

لا أستطيع أن أرى ما do while يقوم به.لماذا لا أكتب هذا دون هذا ؟

#define FOO(X) f(X); g(X)
هل كانت مفيدة؟

المحلول

على do ... while و if ... else هناك لجعله بحيث الفاصلة المنقوطة بعد الماكرو الخاص بك يعني دائما نفس الشيء.دعونا نقول لكم كان شيئا مثل الثاني الماكرو.

#define BAR(X) f(x); g(x)

الآن إذا كنت تريد استخدام BAR(X); في if ... else البيان, حيث الجثث من البيان إذا لم تكن ملفوفة في الأقواس ستحصل على مفاجأة سيئة.

if (corge)
  BAR(corge);
else
  gralt();

رمز أعلاه أن التوسع في

if (corge)
  f(corge); g(corge);
else
  gralt();

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

if (corge)
  {f(corge); g(corge);};
else
  gralt();

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

#define BAR(X) f(X), g(X)

أعلاه نسخة من شريط BAR يوسع رمز أعلاه إلى ما يلي ، والتي هي عبارة صحيحة.

if (corge)
  f(corge), g(corge);
else
  gralt();

هذا لا يعمل إذا كان بدلا من f(X) لديك أكثر تعقيدا الجسم من التعليمات البرمجية التي تحتاج إلى الذهاب في كتلة نقول على سبيل المثال أن تعلن المتغيرات المحلية.في حالة الحل هو استخدام شيء من هذا القبيل do ... while تسبب الماكرو إلى بيان واحد أن يأخذ منقوطة دون التباس.

#define BAR(X) do { \
  int i = f(X); \
  if (i > 4) g(i); \
} while (0)

لم يكن لديك إلى استخدام do ... while, يمكنك طهي شيء مع if ... else كذلك ، على الرغم من أن عندما if ... else يوسع داخل أحد if ... else فإنه يؤدي إلى "التعلق آخر"،والتي يمكن أن تجعل القائمة التعلق آخر مشكلة أصعب أن تجد كما في التعليمات البرمجية التالية.

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();

وهذه النقطة هي أن استخدام الفاصلة المنقوطة في سياقات حيث التعلق منقوطة خاطئ.بالطبع ربما (وربما ينبغي) أن يكون القول في هذه النقطة أنه سيكون من الأفضل أن تعلن BAR باعتبارها وظيفة فعلية ، وليس ماكرو.

في الموجز ، do ... while هل هناك للتغلب على أوجه القصور في ج المعالج.عند تلك C أسلوب أقول لك أن تقلع ج المعالج, هذا هو النوع من الشيء إنهم قلقون.

نصائح أخرى

وحدات الماكرو نسخ/لصق قطع من النص ما قبل المعالج سوف يضع في الحقيقية المدونة ؛ الماكرو هو ويأمل المؤلف استبدال سوف تنتج رمز صالح.

هناك ثلاثة "نصائح" أن تنجح في ذلك:

مساعدة الماكرو تتصرف مثل رمز أصلي

القانون العادي عادة ما انتهت شبه القولون.يجب على المستخدم عرض التعليمات البرمجية لا تحتاج إلى واحد...

doSomething(1) ;
DO_SOMETHING_ELSE(2)  // <== Hey? What's this?
doSomethingElseAgain(3) ;

هذا يعني أن المستخدم يتوقع مترجم لإنتاج خطأ إذا منقوطة غائب.

ولكن الحقيقي لسبب وجيه هو أنه في بعض الوقت الكلي المؤلف قد تحتاج إلى استبدال الماكرو حقيقية وظيفة (ربما المضمنة).لذلك الماكرو يجب حقا تتصرف وكأنها واحدة.

لذا علينا أن الكلية تحتاج إلى شبه القولون.

تنتج رمز صالح

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

if(bIsOk)
   MY_MACRO(42) ;

هذا الماكرو يمكن توسيعها كما:

#define MY_MACRO(x) f(x) ; g(x)

if(bIsOk)
   f(42) ; g(42) ; // was MY_MACRO(42) ;

على g وظيفة سيتم تنفيذها بغض النظر عن قيمة bIsOk.

وهذا يعني أنه يجب عليك إضافة نطاق الماكرو:

#define MY_MACRO(x) { f(x) ; g(x) ; }

if(bIsOk)
   { f(42) ; g(42) ; } ; // was MY_MACRO(42) ;

تنتج رمز صالح 2

إذا كان الماكرو هو شيء من هذا القبيل:

#define MY_MACRO(x) int i = x + 1 ; f(i) ;

ونحن يمكن أن يكون مشكلة أخرى في التعليمات البرمجية التالية:

void doSomething()
{
    int i = 25 ;
    MY_MACRO(32) ;
}

لأن ذلك من شأنه توسيع:

void doSomething()
{
    int i = 25 ;
    int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}

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

#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }

void doSomething()
{
    int i = 25 ;
    { int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}

رمز يتصرف بشكل صحيح مرة أخرى.

الجمع بين شبه القولون + نطاق الآثار ؟

هناك واحد C/C++ لغة أن ينتج هذا التأثير:تفعل/في حين حلقة:

do
{
    // code
}
while(false) ;

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

المكافأة ؟

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

#define MY_MACRO(x)                                  \
do                                                   \
{                                                    \
    const int i = x + 1 ;                            \
    f(i) ; g(i) ;                                    \
}                                                    \
while(false)

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
      MY_MACRO(42) ;

   // Etc.
}

سوف تتوسع بشكل صحيح كما

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
      do
      {
         const int i = 42 + 1 ; // was MY_MACRO(42) ;
         f(i) ; g(i) ;
      }
      while(false) ;

   // Etc.
}

ثم جمعت الأمثل بعيدا عن

void doSomething(bool bIsOk)
{
   int i = 25 ;

   if(bIsOk)
   {
      f(43) ; g(43) ;
   }

   // Etc.
}

@jfm3 - لديك لطيفة الإجابة على السؤال.قد تحتاج أيضا إلى إضافة هذا الماكرو لغة يمنع أيضا ربما أكثر خطورة (لأنه لا يوجد خطأ) السلوك غير المرغوب فيه مع بسيطة 'إذا' البيانات:

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

توسع:

if (test) f(baz); g(baz);

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

الإجابات أعلاه شرح معنى هذه التركيبات ، ولكن هناك فرق كبير بين اثنين لم تذكر.في الواقع ، هناك سبب تفضل do ... while إلى if ... else بناء.

مشكلة if ... else بناء هو أنه لا القوة يمكنك وضع الفاصلة المنقوطة.كما في هذا الكود:

FOO(1)
printf("abc");

على الرغم من أننا تركت منقوطة (عن طريق الخطأ) ، رمز توسيع

if (1) { f(X); g(X); } else
printf("abc");

وسوف بصمت ترجمة (على الرغم من أن بعض المجمعين قد يصدر تحذير رمز تصل).ولكن printf البيان لن يعدم.

do ... while بناء لا يملك مثل هذه المشكلة منذ صالحة فقط رمزية بعد while(0) هو منقوطة.

في حين أنه من المتوقع أن المجمعين تحسين بعيدا do { ... } while(false); الحلقات ، هناك حل آخر لا تتطلب بناء.الحل هو استخدام الفاصلة المشغل:

#define FOO(X) (f(X),g(X))

أو حتى أكثر اكسوتيكالي:

#define FOO(X) g((f(X),(X)))

في حين أن هذا سوف تعمل بشكل جيد مع تعليمات منفصلة ، فهو لن يعمل مع الحالات التي تكون فيها المتغيرات هي التي شيدت وتستخدم كجزء من #define :

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

مع هذا واحد سوف تضطر إلى استخدام تفعل/في حين بناء.

ينس Gustedt هو P99 المعالج المكتبة (نعم, حقيقة أن مثل هذا الشيء موجود فجر ذهني أيضا!) يحسن على if(1) { ... } else بناء صغيرة ولكن هامة من خلال تحديد التالية:

#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

والأساس المنطقي لهذا هو أنه على عكس do { ... } while(0) بناء ، break و continue لا تزال تعمل داخل بالنظر إلى كتلة ، ولكن ((void)0) يخلق خطأ في بناء الجملة إذا كانت الفاصلة المنقوطة بعد حذف الماكرو الدعوة ، التي من شأنها أن خلاف ذلك تخطي كتلة التالي.(ليس هناك في الواقع "التعلق آخر" المشكلة هنا منذ else يربط إلى أقرب if, الذي هو واحد في الماكرو.)

إذا كنت مهتما في أنواع الأشياء التي يمكن القيام به أكثر أو أقل بأمان مع ج المعالج, تحقق من تلك المكتبة.

لبعض الأسباب لا أستطيع التعليق على الجواب الأول...

بعض أظهرت وحدات الماكرو مع المتغيرات المحلية, لكن لا أحد ذكر أنه لا يمكنك استخدام أي اسم في ماكرو!سوف يعض المستخدم بعض يوم!لماذا ؟ لأن المدخلات الحجج بديلا في الماكرو الخاص بك القالب.و في الماكرو الخاص بك أمثلة لقد استخدم على الأرجح الأكثر شيوعا variabled اسم أنا.

على سبيل المثال عندما الماكرو التالي

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

يستخدم في الدالة التالية

void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

الماكرو لن تستخدم المقصود المتغير i ، التي أعلنت في بداية some_func ، ولكن المتغير المحلي ، التي أعلنت في القيام ...في حين حلقة من الماكرو.

وبالتالي لا الشائعة أسماء المتغيرات في ماكرو!

تفسير

do {} while (0) و if (1) {} else هي للتأكد من أن الكلي هو توسيع 1 فقط التعليمات.خلاف ذلك:

if (something)
  FOO(X); 

ستوسع إلى:

if (something)
  f(X); g(X); 

و g(X) إعدام خارج if التحكم في بيان.هذا هو تجنبها عند استخدام do {} while (0) و if (1) {} else.


أفضل بديل

مع جنو بيان التعبير (ليس جزءا من معيار ج) لديك طريقة أفضل من do {} while (0) و if (1) {} else لحل هذا ببساطة عن طريق استخدام ({}):

#define FOO(X) ({f(X); g(X);})

و هذه الجملة متوافق مع عودة القيم (لاحظ أن do {} while (0) لا), كما في:

return FOO("X");

أنا لا أعتقد أنه ذكر ذلك النظر في هذا

while(i<100)
  FOO(i++);

سوف تترجم إلى

while(i<100)
  do { f(i++); g(i++); } while (0)

لاحظ كيف i++ يتم تقييم مرتين من قبل الكلية.هذا يمكن أن يؤدي إلى بعض الأخطاء.

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

#define CALL_AND_RETURN(x)  if ( x() == false) break;
do {
     CALL_AND_RETURN(process_first);
     CALL_AND_RETURN(process_second);
     CALL_AND_RETURN(process_third);
     //(simply add other calls here)
} while (0);
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top