لغة سي و متحكماتمقالات

C preprocessor الجزء 2

تحدثنا في الجزء الأول عن المفهوم النظري وأساسيات الاستخدام لـ C preprocessor وسنركز في هذا الجزء على أمور أكثر تقدماً.

function-like macros

تذكير: تسمى مجموعة الأسطر البرمجية المعرفة عبر define بـmacros .

يمكننا من خلال الC preprocessor إنجاز ما يشبه التوابع ولذلك تسمى function-like مع اختلاف جوهري بين التوابع و function-like macros، شكل الشبَه الوحيد هو إمكانية إنجاز macros يمكن أن تأخذ وسطاء، أما من الناحية التنفيذية فليس هناك أي وجه شبه:

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

  • Function-like macros : مجموعة من الأسطر البرمجية المختصرة برمز، و عند إيراد هذا الرمز في الكود فهو بمثابة إيراد هذه الأسطر البرمجية كما هي، بخلاف مبدأ التوابع المعتمد على الاستدعاء مع وجود الكود دون تكرار .

لابد التنويه إلى الجانب السلبي لاستخدام الC preprocessor وهو حجم البرنامج النهائي، إذا أن استخدام الC preprocessor لإنجاز ما يشبه التوابع function-like macros يؤدي إلى تضاعف حجم البرنامج، فكما قلنا في الجزء الأول استخدام الC preprocessor لا يتعدى في النهاية عن اختصار أسطر برمجية بكلمات مفتاحية ولكنها من ناحية الأداء لا تختلف لو كتبنا الأسطر البرمجية بدل الكلمات المفتاحية، بعكس التوابع التي مهما كررنا استدعاءها فإن الكود واحد لا يتكرر.

النحوية function-like macros syntax

لإنجاز macros يأخد وسيط يجب أن نتبع اسم المايكرو (الرمزبأقواس متوسطة تحوي أسماء الوسطاء مباشرة ، دون تحديد نوعها كما في التوابع، إذ لا يهم الfunction-like macros نوع الوسطاء إذ لا تتعامل معها كوسطاء حقيقيين وإنما كقيم مجردة سيتم تمريرها إلى تعريف الmacros.

عودة إلى الكود في نهاية الجزء الأول

/* ———— Macros For Building BitFields ————– */
#define BIT_SET(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define BIT_CLEAR(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
#define BIT_WRITE(n,ADDRESS,BIT) (n ? BIT_SET(ADDRESS,BIT) : BIT_CLEAR(ADDRESS,BIT))
#define BIT_CHECK(ADDRESS,BIT) (ADDRESS & (1<<BIT))
#define BIT_FLIP(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))
#define BIT_GET(ADDRESS,BIT) (ADDRESS & (1<<BIT))
/* ——————– Example Usage ————————-
BIT_SET(PORTB,1);
if(BIT_GET(PINB,1) == 0) BIT_SET(PORTB,5);
else BIT_CLEAR(PORTB,5);
/- ——————————————————— */

سنأخذ أحد الfunction-like macros كمثال:

#define BIT_SET(ADDRESS,BIT) (ADDRESS |= (1<<BIT))

اسم macro هو BIT_SET والوسطاء هم ADDRESS,BIT وعند الاستخدام نمرر الوسطاء التي نريد، مثال:

BIT_SET(PORTB,5);

وهذا مكافئ تماماً للتالي:

(PORTB |= (1<<5)) ;

ملاحظات في function-like macros :

1- function-like macros لا تقوم بعمليات حسابية للوسطاء.

لنفترض الfunction-like macro التالي:

#define multi(x,y) x*y/100

ولنفرض استخدامها كالتالي:

int r=1 ;
int res;
res=multi(r+1,r+2) ;

كما قلنا C preprocessor لا يقوم بتنفيذ الكود وبالتالي لن يقوم بالعمليات الحسابية r+1 أو r+2 لأن الـpreprocessor لا يستطيع التنفيذ مهما كانت العملية بسيطة ، وهذا مكافئ للتالي :

res=r+1*r+2/100 ;

وبالتالي لتجنب هذا الخطأ يجب أن نستخدم الأقواس ()، كالتالي:

#define sum(x,y,z) (x)*(y)/100

وعند الاستخدام السابق، ستكون النتيجة بعد مرحلة preprocessor:

res=(r+1)*(r+2)/100 ;

2-الأقواس تتبع اسم macro مباشرة.

لنفرض الحالتين التاليتين

#define multi(x,y) x*y/100
#define multi (x,y) x*y/100

إن الأول هو function-like أما الثاني فهو macro عادي كما لو أننا عرفنا الرمز sum فقط، لذلك يجب الانتباه أن نضع الأقواس مباشرة بعد اسم الmacro في حال وجود الوسطاء.

3-C preprocessor scope

عند ذكر عبارات تعريف متحولات داخل الmacro يجب الانتباه أنها لا تشابه التوابع من ناحية إنشاء local variables، ففي التوابع عند تعريف متحول يبقى مجال رؤية واستخدام المتحول محصور داخل التابع نفسه، أما الC preprocessor فهي كما قلنا لا تتعدى اختصار مجموعة أكواد برمز وعند ذكر الرمز نكون ضمنا هذه الأسطر ضمناً وبالتالي وجب الانتباه ومن أحد الحلول هي إنشاء كيان وهمي بضمن تعريف المتحولات بشكل محلي وذلك باستخدام do while(0)  كالتالي.

#define swap(x,y)  do{int temp; \
temp=x; \
x=y ; \
y=temp;}
while(0)

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

من أخطاء الscope أيضاً عدم الانتباه لحالة كالتالي:

if(A>B) swap(A,B);

إن هذا مكافئ لتنفيذ أول تعليمة فقط من تعريف swap، أي تكافئ تعريف المتحول temp وتنفيذ البقية خارج الشرط :

if(A>B) 
int temp;
temp=x;
x=y ;
y=temp ;

لحل هذه المشكلة يمكن أن نقوم بأحد الإجرائين:

الحل الأول: تغيير طريقة ذكر اسم الـmacro

if(A>B) {swap(A,B)};

الحل الثاني: تغيير طريقة تعريف الـmacro

#define swap(x,y) {int temp; \
temp=x; \
x=y ; \
y=temp}

استخدام الـ (##)concatenation

يستخدم الرمز ## في أخذ الوسطاء وإضافتها للأوامر التوجيهية directives

مثال :

#define pinMode(num,type)  porta_pin_##num_##type()
#define porta_pin_1_output() (TRISAbits.TRISA1 = 0)
#define porta_pin_1_input() (TRISAbits.TRISA1 = 1) 

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

pinMode(1,output)

في الجزء الثالث والأخير سنتحدث عن استخدام الـC preprocessor في الترجمة الشرطية conditional compilation بالإضافة إلى إيراد بعض الأمثلة العملية.

[related_posts_by_tax title=”مقالات مرتبطة” format=”thumbnails” image_size=”medium” taxonomies=”articles” posts_per_page=”3″ link_caption=1]

Yahya Tawil

يحيى هو مدير التحرير في عتاديات ويؤمن بأهمية المحتوى المكتوب المجاني والنوعي والعملي. خبرته في مجال النظم المضمّنة تتركز في كتابة البرامج المضمنة وتصميم الدارات المطبوعة والنظرية وإنشاء المحتوى.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني.

هذا الموقع يستخدم Akismet للحدّ من التعليقات المزعجة والغير مرغوبة. تعرّف على كيفية معالجة بيانات تعليقك.

زر الذهاب إلى الأعلى