ما هي تطبيقات ## مشغل المعالج المسبق والمشاكل التي يجب مراعاتها؟

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

سؤال

كما ذكرنا في العديد من أسئلتي السابقة، فأنا أعمل من خلال K&R، وأنا حاليًا في مرحلة المعالجة المسبقة.أحد الأشياء الأكثر إثارة للاهتمام - وهو شيء لم أعرفه من قبل من أي من محاولاتي السابقة لتعلم لغة C - هو ## مشغل المعالج المسبق.وفقًا لـ K&R:

عامل المعالج المسبق ##يوفر وسيلة لتسلسل الحجج الفعلية أثناء توسيع الماكرو.إذا كانت المعلمة في النص البديل مجاورًا لـ ##, ، يتم استبدال المعلمة بالوسيطة الفعلية ، ## وتتم إزالة المساحة البيضاء المحيطة ، ويتم إعادة تصور النتيجة.على سبيل المثال، الماكرو pasteيسلسل الوسيطتين:

#define paste(front, back) front ## back

لذا paste(name, 1) يخلق الرمز المميز name1.

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

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

المحلول

كراشربت:استخدام ## لتحويل سلاسل الماكرو متعددة البايت إلى Unicode

الاستخدام المثير للاهتمام في CrashRpt (مكتبة تقارير الأعطال) هو ما يلي:

#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
//Note you need a WIDEN2 so that __DATE__ will evaluate first.

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

 std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

يستخدمونه مع ماكرو آخر يقوم بإرجاع سلسلة بالتاريخ والوقت.

وضع L بجانب أ __ DATE __ سيعطيك خطأ في التجميع.


شبابيك:استخدام ## لسلاسل Unicode العامة أو متعددة البايت

يستخدم Windows شيئًا مثل ما يلي:

#ifdef  _UNICODE
    #define _T(x)      L ## x
#else
    #define _T(x) x
#endif

و _T يتم استخدامه في كل مكان في التعليمات البرمجية


مكتبات مختلفة تستخدم لأسماء الملحقات والمُعدِّلات النظيفة:

لقد رأيته أيضًا مستخدمًا في التعليمات البرمجية لتحديد أدوات الوصول والمعدلات:

#define MYLIB_ACCESSOR(name) (Get##name)
#define MYLIB_MODIFIER(name) (Set##name)

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


مكتبات مختلفة، تستخدمها لإصدار عدة إعلانات للمتغيرات في وقت واحد:

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;

نصائح أخرى

وشيء واحد أن تكون على علم عندما كنت تستخدم الرمز المميز لصق ( '##') أو stringizing ( '#') تجهيزها مشغلي هو أن لديك لاستخدام مستوى إضافي من indirection لهم للعمل بشكل صحيح في جميع الحالات.

إذا كنت لا تفعل هذا، والبنود التي تم تمريرها إلى المشغل رمز اللصق وحدات الماكرو أنفسهم، وستحصل على النتائج التي هي على الأرجح ليس ما تريد:

#include <stdio.h>

#define STRINGIFY2( x) #x
#define STRINGIFY(x) STRINGIFY2(x)
#define PASTE2( a, b) a##b
#define PASTE( a, b) PASTE2( a, b)

#define BAD_PASTE(x,y) x##y
#define BAD_STRINGIFY(x) #x

#define SOME_MACRO function_name

int main() 
{
    printf( "buggy results:\n");
    printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__)));

    printf( "\n" "desired result:\n");
    printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__)));
}

وإخراج:

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21

إليك المشكلة التي واجهتها عند الترقية إلى إصدار جديد من المترجم:

الاستخدام غير الضروري لعامل لصق الرمز المميز (##) غير محمول وقد يؤدي إلى إنشاء مسافات بيضاء أو تحذيرات أو أخطاء غير مرغوب فيها.

عندما لا تكون نتيجة عامل لصق الرمز المميز رمزًا صالحًا للمعالج المسبق، فإن عامل لصق الرمز المميز يكون غير ضروري وربما يكون ضارًا.

على سبيل المثال، قد يحاول المرء إنشاء سلسلة حرفية في وقت الترجمة باستخدام عامل لصق الرمز المميز:

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a##+##b)
#define NS(a, b) STRINGIFY(a##::##b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

في بعض المترجمين، سيؤدي هذا إلى إخراج النتيجة المتوقعة:

1+2 std::vector

في المترجمات الأخرى، سيتضمن هذا مسافة بيضاء غير مرغوب فيها:

1 + 2 std :: vector

ستفشل الإصدارات الحديثة إلى حد ما من مجلس التعاون الخليجي (>= 3.3 أو نحو ذلك) في ترجمة هذا الرمز:

foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token
foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token
foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token
foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token

الحل هو حذف عامل لصق الرمز المميز عند ربط الرموز المميزة للمعالج المسبق مع مشغلي C/C++:

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a+b)
#define NS(a, b) STRINGIFY(a::b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

ال فصل وثائق CPP لدول مجلس التعاون الخليجي حول التسلسل يحتوي على المزيد من المعلومات المفيدة حول عامل لصق الرمز المميز.

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

#define LOAD_IMGLIB_FN(lib,func) {                                      \
    fn_##func = (void *) GetProcAddress (lib, #func);                   \
    if (!fn_##func) return 0;                                           \
  }

وبعد ذلك يمكننا استخدامه:

LOAD_IMGLIB_FN (library, XpmFreeAttributes);
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
LOAD_IMGLIB_FN (library, XpmReadFileToImage);
LOAD_IMGLIB_FN (library, XImageFree);

والفائدة هو عدم وجود لكتابة كل من fn_XpmFreeAttributes و"XpmFreeAttributes" (وإملائيا خطر واحد منهم).

طرح سؤال سابق حول Stack Overflow طريقة سلسة لإنشاء تمثيلات سلسلة لثوابت التعداد دون الكثير من عمليات إعادة الكتابة المعرضة للأخطاء.

وصلة

أظهرت إجابتي على هذا السؤال كيف يتيح لك تطبيق القليل من سحر المعالج المسبق تحديد التعداد الخاص بك مثل هذا (على سبيل المثال) ...؛

ENUM_BEGIN( Color )
  ENUM(RED),
  ENUM(GREEN),
  ENUM(BLUE)
ENUM_END( Color )

...مع فائدة أن توسيع الماكرو لا يحدد التعداد فقط (في ملف .h)، بل يحدد أيضًا مصفوفة مطابقة من السلاسل (في ملف .c)؛

const char *ColorStringTable[] =
{
  "RED",
  "GREEN",
  "BLUE"
};

يأتي اسم جدول السلسلة من لصق معلمة الماكرو (أي.Color) إلى StringTable باستخدام عامل التشغيل ##.التطبيقات (الحيل؟) مثل هذه هي حيث تكون عوامل التشغيل # و## لا تقدر بثمن.

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

SCREEN_HANDLER( activeCall )

ويتسع لشيء من هذا القبيل:

STATUS activeCall_constructor( HANDLE *pInst )
STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
STATUS activeCall_destructor( HANDLE *pInst );

وهذا يفرض المعايير والثوابت الصحيح للجميع "مشتق" الكائنات عند القيام به:

SCREEN_HANDLER( activeCall )
SCREEN_HANDLER( ringingCall )
SCREEN_HANDLER( heldCall )

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

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

وأنا استخدامها لشراء منزل توالت ASSERT على مترجم C غير قياسي للجزءا لا يتجزأ من:



#define ASSERT(exp) if(!(exp)){ \
                      print_to_rs232("Assert failed: " ## #exp );\
                      while(1){} //Let the watchdog kill us 


ويمكنك استخدام لصق رمزيا عندما كنت في حاجة لسلسلة المعلمات الكلي مع شيء آخر.

ويمكن استخدامه لقوالب:

#define LINKED_LIST(A) struct list##_##A {\
A value; \
struct list##_##A *next; \
};

في هذه الحالة LINKED_LIST (كثافة) من شأنها أن تعطيك

struct list_int {
int value;
struct list_int *next;
};

وبالمثل يمكن أن تكتب قالب وظيفة لائحة اجتياز.

وأنا استخدامها لإضافة البادئات مخصصة للمتغيرات التي يحددها وحدات الماكرو. ذلك شيء من هذا القبيل:

UNITTEST(test_name)

وتتسع ل:

void __testframework_test_name ()

والاستخدام الرئيسي هو عندما يكون لديك اصطلاح التسمية وتريد الخاص بك الكلي للاستفادة من تلك الاتفاقية التسمية. ربما لديك العديد من العائلات من الطرق: image_create ()، image_activate ()، وimage_release () أيضا file_create ()، file_activate ()، file_release ()، وmobile_create ()، mobile_activate () وmobile_release ()

.

هل يمكن كتابة ماكرو للتعامل مع دورة حياة الكائن:

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())

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

واحد استخدام مهم في ويندوز سي:

#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))

وبينما وصف تعريف بت تسجيل نحن لا التالية:

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

وأثناء استخدام BITFMASK، ببساطة استخدام:

BITFMASK(ADDR)

ومن المفيد جدا للتسجيل. يمكنك القيام به:

#define LOG(msg) log_msg(__function__, ## msg)

وأو، إذا لا يدعم المترجم الخاص بك على وظيفة و <قوية> ظائفها : ل

#define LOG(msg) log_msg(__file__, __line__, ## msg)

وو"وظائف" المذكورة أعلاه بتسجيل رسالة ويظهر بالضبط التي تعمل تسجيل رسالة.

وبلدي جملة C ++ قد يكون غير صحيح تماما.

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