معالج مسبق للغة C

معالج C المسبق ( CPP ) هو معالج ملفات نصية يُستخدم مع لغات البرمجة C و C++ وغيرها . يوفر هذا المعالج إمكانية تضمين الملفات (وخاصة ملفات الرأس )، وتوسيع وحدات الماكرو ، والترجمة الشرطية ، والتحكم في الأسطر. على الرغم من تسميته واستخدامه مع لغة C، إلا أن إمكانياته لا تقتصر عليها، بل يمكن استخدامه لمعالجة أنواع أخرى من الملفات. [ 1 ]

اللغات

توفر مُجمِّعات لغات C وC++ و Objective-C إمكانية المعالجة المسبقة، كما هو مطلوب في تعريف كل لغة. وتُتيح بعض المُجمِّعات امتدادات وانحرافات عن معيار اللغة الهدف. كما تُتيح بعضها خيارات للتحكم في التوافق مع المعايير. على سبيل المثال، يُمكن جعل مُعالج GNU C المسبق أكثر توافقًا مع المعايير من خلال توفير بعض علامات سطر الأوامر. [ 2 ]

تتيح لغة البرمجة C# أيضًا استخدام التوجيهات ، مع أنها لا تُقرأ بواسطة معالج مسبق ولا يمكن استخدامها لإنشاء وحدات ماكرو، وهي مُخصصة بشكل عام لميزات مثل التجميع الشرطي. [ 3 ] نادرًا ما تتطلب C# استخدام هذه التوجيهات، فعلى سبيل المثال، لا يتطلب تضمين التعليمات البرمجية معالجًا مسبقًا على الإطلاق (بما أن C# تعتمد على نظام الحزم/مساحات الأسماء مثل Java، فلا حاجة لتضمين أي تعليمات برمجية). وبالمثل، تستطيع F# و Visual J# استدعاء توجيهات المعالج المسبق الخاصة بـ C#.

تسمح لغة البرمجة Haskell أيضًا باستخدام المعالج المسبق للغة C.

يتم ترميز خصائص المعالج المسبق في شفرة المصدر على شكل توجيهات تبدأ بـ #.

على الرغم من أن ملفات مصدر C++ غالبًا ما تحمل امتداد .cpp ، إلا أن هذا اختصار لـ "C plus plus"؛ وليس معالج C المسبق.

توجيهات المعالج المسبق

اللغات التالية لديها التوجيهات المقبولة التالية.

لغة C/C++

يتم التعرف على الرموز التالية بواسطة المعالج المسبق في سياق توجيهات المعالج المسبق.

  • #if
  • #elif
  • #else
  • #endif
  • #ifdef
  • #ifndef
  • #elifdef
  • #elifndef
  • #define
  • #undef
  • #include
  • #embed
  • #line
  • #error
  • #warning
  • #pragma
  • defined(يتبع توجيهًا شرطيًا؛ ليس توجيهًا فعليًا، بل عاملًا)
  • __has_include(المشغل)
  • __has_cpp_attribute(المشغل)
  • __has_c_attribute(المشغل)
  • __has_embed(المشغل)

حتى C++26 ، كانت الكلمات الرئيسية C++ import، exportو ، moduleيتم التعامل معها جزئيًا بواسطة المعالج المسبق أيضًا.

تقبل لغة البرمجة هاسكل أيضًا توجيهات المعالج المسبق للغة سي، والتي يتم استدعاؤها بكتابة الأمر {-# LANGUAGE CPP #-}في بداية الملف. وتتوافق توجيهات المعالج المسبق المقبولة مع تلك الموجودة في لغتي سي/سي++ القياسيتين.

سي شارب

على الرغم من أن C# و F# [ 4 ] و Visual J# لا تحتوي على معالج مسبق منفصل، إلا أن هذه التوجيهات تتم معالجتها كما لو كان هناك واحد.

  • #nullable
  • #if
  • #elif
  • #else
  • #endif
  • #define
  • #undef
  • #region
  • #endregion
  • #error
  • #warning
  • #line
  • #pragma

لا تستخدم لغة C# معالجًا مسبقًا للتعامل مع هذه التوجيهات، وبالتالي لا يتم التعامل معها أو إزالتها بواسطة معالج مسبق، بل تتم قراءتها مباشرة بواسطة مترجم C# كميزة من ميزات اللغة.

أوبجكتيف سي

يتم التعرف على الرموز التالية بواسطة المعالج المسبق في سياق توجيهات المعالج المسبق.

  • #if
  • #elif
  • #else
  • #endif
  • #ifdef
  • #ifndef
  • #define
  • #undef
  • #include
  • #import
  • #error
  • #pragma
  • defined

سويفت

تُكتشف الرموز التالية، والتي تُسمى "توجيهات المُترجم"، بواسطة المُترجم. وهي ليست مُطابقة تمامًا لتوجيهات مُعالج لغة C المُسبقة، ولكنها تتشابه معها في البنية والتركيب.

  • #if
    • arch(...)
    • canImport(...)
    • compiler(...)
    • hasAttribute(...)
    • hasFeature(...)
    • os(...)
    • swift(...)
    • targetEnvironment(...)
  • #elseif
  • #else
  • #endif
  • #available
  • #unavailable
  • #error
  • #warning
  • #file
  • #fileID
  • #filePath
  • #line
  • #column
  • #function
  • #dsohandle
  • #sourceLocation
  • #selector
  • #keyPath
  • #colorLiteral
  • #imageLiteral
  • #fileLiteral
  • #stringify
  • #externalMacro

تاريخ

أُدخل المعالج المسبق إلى لغة C حوالي عام 1973 بناءً على اقتراح آلان سنايدر، وأيضًا تقديرًا لفائدة آليات تضمين الملفات المتوفرة في لغتي BCPL و PL/I . قدّم الإصدار الأول تضمين الملفات عبر #includeواستبدال السلاسل النصية بدون معلمات عبر #define. وتم توسيعه بعد ذلك بفترة وجيزة، أولًا بواسطة مايك ليسك ثم بواسطة جون رايزر، لإضافة وسائط إلى وحدات الماكرو ودعم الترجمة الشرطية . [ 5 ]

كان المعالج المسبق للغة C جزءًا من تقليد طويل للغات الكبيرة في مختبرات بيل، والذي بدأه دوغلاس إيستوود ودوغلاس ماكلروي في عام 1959. [ 6 ]

المراحل

يتم تعريف المعالجة المسبقة من خلال المراحل الأربع الأولى (من أصل ثماني) من الترجمة المحددة في معيار C.

  1. استبدال الثلاثيات: يقوم المعالج المسبق باستبدال تسلسلات الثلاثيات بالأحرف التي تمثلها. وقد أُزيلت هذه المرحلة في C23 بعد خطوات C++17 .
  2. وصل الأسطر: يتم وصل أسطر المصدر المادية التي تستمر بتسلسلات أسطر جديدة مهربة لتشكيل أسطر منطقية.
  3. التجزئة: يقوم المعالج المسبق بتقسيم النتيجة إلى رموز معالجة مسبقة ومسافات بيضاء . ويستبدل التعليقات بمسافات بيضاء.
  4. توسيع وحدات الماكرو ومعالجة التوجيهات: يتم تنفيذ سطور توجيهات المعالجة المسبقة، بما في ذلك تضمين الملفات والتجميع الشرطي. يقوم المعالج المسبق بتوسيع وحدات الماكرو في الوقت نفسه، ومنذ إصدار 1999 من معيار لغة C، يتعامل مع _Pragmaعوامل التشغيل.

سمات

تضمين الملف

يوجد توجيهان في معالج C المسبق لإدراج محتويات الملفات:

  • #include، تُستخدم لإدراج محتويات ملف بشكل مباشر في مكانه (عادةً ما يحتوي على نوع من التعليمات البرمجية)
  • #embed، تُستخدم لإدراج أو تضمين محتويات مورد ثنائي مباشرةً في مكانه

تضمين الكود

لإدراج محتوى ملف في ملف آخر، يستبدل المعالج المسبق السطر الذي يبدأ بـ `<file>` #includeبمحتوى الملف المحدد بعد التوجيه `<file>`. قد يكون الإدراج منطقيًا بمعنى أن المحتوى الناتج قد لا يُخزن على القرص، وبالتأكيد لا يُستبدل بمحتوى الملف الأصلي. لا يشترط أن يحتوي الملف المُدرج على أي نوع من التعليمات البرمجية، لأن هذا التوجيه سينسخ محتوى أي ملف مُدرج في مكانه، ولكن الاستخدام الأكثر شيوعًا لـ `<file>` #includeهو إدراج ملف رأس (أو في حالات نادرة، ملف مصدر).

في مثال التعليمات البرمجية التالي، يقوم المعالج المسبق باستبدال السطر بمحتوى ملف رأس المكتبة القياسية المسمى ' stdio.h ' والذي يتم فيه تعريف الدالة والرموز الأخرى.#include<stdio.h>printf()

#include <stdio.h>int main ( void ) { printf ( "Hello, World! \n " ); return 0 ; }

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

في لغة C، يُسمى ملف الرأس عادةً بالامتداد .h . أما في لغة C++، فيختلف اصطلاح امتداد الملف، حيث يشيع استخدام الامتدادين .h و .hpp . لكن المعالج المسبق يُضمّن الملف بغض النظر عن الامتداد. في الواقع، يتضمن الكود أحيانًا ملفات بامتداد .c أو .cpp .

لمنع تضمين نفس الملف عدة مرات، الأمر الذي يؤدي غالبًا إلى خطأ في المترجم، يحتوي ملف الرأس عادةً على #include guard أو إذا كان مدعومًا من قبل المعالج المسبق #pragma once لمنع التضمين المتعدد.

تضمين الموارد الثنائية

تُقدّم لغتا C23 و C++26#embed توجيهًا لتضمين الموارد الثنائية ، يسمح بتضمين محتوى ملف ثنائي في الكود المصدري حتى لو لم يكن كود C صالحًا. [ 7 ] [ 8 ] يتيح ذلك تضمين الموارد الثنائية (مثل الصور) في البرنامج دون الحاجة إلى معالجتها بواسطة أدوات خارجية، xxd -iودون استخدام السلاسل النصية ، التي لها حد أقصى للطول في MSVC . على غرار التوجيه السابق xxd -i، يُستبدل التوجيه بقائمة من الأعداد الصحيحة مفصولة بفواصل، تُطابق بيانات المورد المُحدد. بتعبير أدق، إذا تم تهيئة مصفوفة من النوع باستخدام توجيه، فإن النتيجة تكون مماثلة لكتابة المورد إلى المصفوفة باستخدام (إلا إذا غيّر مُعامل عرض عنصر التضمين إلى قيمة أخرى غير ). إلى جانب سهولة الاستخدام، يُعدّ التوجيه أسهل في التعامل بالنسبة للمُترجمات، حيث يُسمح لها بتجاوز توسيع التوجيه إلى شكله الكامل بفضل قاعدة "كما لو" .unsignedchar#embedfreadCHAR_BIT#embed

يُحدد الملف المراد تضمينه بنفس طريقة تحديد الملف المُضمّن #include- إما باستخدام الأقواس أو علامات الاقتباس المزدوجة. يسمح هذا التوجيه أيضًا بتمرير معلمات معينة لتخصيص سلوكه. يُحدد معيار لغة C بعض المعلمات، وقد تُحدد التطبيقات معلمات إضافية. limitتُستخدم المعلمة `limit` لتقييد عرض البيانات المُضمنة. وهي مُخصصة في الغالب للاستخدام مع الملفات "غير المحدودة" مثل urandom . تسمح المعلمتان `prefix` و`flash` بتحديد بادئة ولاحقة للبيانات المُضمنة. أخيرًا، تستبدل المعلمة `limit` التوجيه بأكمله إذا كان المورد فارغًا. يمكن إحاطة جميع المعلمات القياسية بشرطتين سفليتين مزدوجتين، تمامًا مثل السمات القياسية في C23، على سبيل المثال، `limit` قابلة للتبديل مع `limit` . prefixتستخدم المعلمات المُحددة من قِبل التطبيق شكلًا مشابهًا لصيغة السمات (مثل `limit` )، ولكن بدون الأقواس المربعة. في حين أن جميع المعلمات القياسية تتطلب تمرير وسيطة إليها (على سبيل المثال، تتطلب `limit` عرضًا)، فإن هذا اختياري بشكل عام، ويمكن حتى حذف مجموعة الأقواس إذا لم تكن الوسيطة مطلوبة، وهو ما قد يكون الحال بالنسبة لبعض المعلمات المُحددة من قِبل التطبيق.suffixif_empty__prefix__prefixvendor::attr

constexpr char ICON_DISPLAY_DATA [] = { #embed "art.png" };// حدد أي نوع يمكن تهيئته من خلال تعبيرات الثوابت العددية الصحيحة constexpr char RESET_BLOB [] = { #embed "data.bin" };// تعمل السمات بنفس الكفاءة alignas ( 8 ) constexpr char ALIGNED_DATA_STRING [] = { #embed "attributes.xml" };int main () { return #embed </dev/urandom> limit(1) ; }

التجميع المشروط

يدعم التجميع الشرطي عبر توجيهات if-else الأساسية #if، #elseو، #elifو، #endifو، بالإضافة إلى توجيهات الاختصار #ifdefو #ifndef، التي تُمثل #if defined(...)و #if !defined(...)على التوالي. في مثال الكود التالي، printf()يتم تضمين الاستدعاء للتجميع فقط إذا VERBOSEتم تعريف .

#ifdef VERBOSE printf ( "رسالة التتبع" ); #endif

يوضح ما يلي منطقًا أكثر تعقيدًا:

#if !(defined __LP64__ || defined __LLP64__) || defined _WIN32 && !defined _WIN64 // كود لنظام 32 بت #else // كود لنظام 64 بت #endif

استبدال سلسلة الماكرو

كائن شبيه

يُحدد الماكرو كيفية استبدال نص في شفرة المصدر بنص آخر. يُعرّف الماكرو الشبيه بالكائن رمزًا يستبدله المعالج المسبق بنص آخر. لا يتضمن الماكرو صيغة المعاملات، وبالتالي لا يدعم استخدام المعاملات. يربط تعريف الماكرو التالي النص 1 / 12بالرمز VALUE:

#حدد القيمة 1 / 12

وظيفة شبيهة

يدعم الماكرو الشبيه بالدالة المعاملات، على الرغم من أن قائمة المعاملات قد تكون فارغة. يربط تعريف الماكرو التالي التعبير (A + B)بالرمز المميز ADDالذي يحتوي على المعاملات Aو B.

#define ADD(A, B) (A + B)

لا يمكن أن يحتوي تعريف الماكرو الشبيه بالدالة على مسافة بيضاء بين الرمز المميز والقوس المفتوح الأول. في حال وجود مسافة بيضاء، يُفسَّر الماكرو على أنه كائن، حيث يبدأ كل شيء من القوس الأول ويُضمَّن في النص البديل.

توسع

يقوم المعالج المسبق باستبدال كل رمز من رموز الكود الذي يطابق رمز الماكرو بالنص البديل المرتبط به، فيما يُعرف بتوسيع الماكرو . تجدر الإشارة إلى أن نص السلاسل النصية والتعليقات لا يُحلل كرموز، وبالتالي يتم تجاهله عند توسيع الماكرو. بالنسبة للماكرو الشبيه بالدالة، تُستبدل معلمات الماكرو أيضًا بالقيم المحددة في مرجع الماكرو. على سبيل المثال، يتوسع إلى .ADD(VALUE,2)1/12+2

متغير

يقبل الماكرو المتغير (الذي تم تقديمه مع C99 ) عددًا متغيرًا من الوسائط، وهو أمر مفيد بشكل خاص عند تغليف الدوال التي تقبل عددًا متغيرًا من المعلمات، مثل printf.

ترتيب التوسع

يحدث التوسع الكلي الشبيه بالوظيفة في المراحل التالية:

  1. يتم استبدال عمليات تحويل النص إلى سلسلة نصية بالتمثيل النصي لقائمة استبدال وسيطاتها (دون إجراء عملية توسيع).
  2. يتم استبدال المعلمات بقائمة الاستبدال الخاصة بها (دون إجراء عملية توسيع).
  3. يتم استبدال عمليات الربط بالنتيجة المدمجة للمعاملين (دون توسيع الرمز المميز الناتج).
  4. يتم توسيع الرموز المميزة الناشئة عن المعلمات.
  5. يتم توسيع الرموز الناتجة كالمعتاد.

قد ينتج عن ذلك نتائج مفاجئة:

#define HE HI #define LLO _THERE #define HELLO "HI THERE" #define CAT(a,b) a##b #define XCAT(a,b) CAT(a,b) #define CALL(fn) fn(HE,LLO) CAT ( HE , LLO ) // "HI THERE"، لأن عملية الربط تحدث قبل التوسيع العادي XCAT ( HE , LLO ) // HI_THERE، لأن الرموز المميزة الناتجة عن المعاملات ("HE" و "LLO") يتم توسيعها أولاً CALL ( CAT ) // "HI THERE"، لأن هذا يُقيّم إلى CAT(a,b)

إلغاء تعريف الماكرو

يمكن إزالة تعريف الماكرو من سياق المعالج المسبق #undefبحيث لا تتوسع أي إشارة لاحقة إلى رمز الماكرو. على سبيل المثال:

#حدد القيمة 15// قم بأشياء باستخدام القيمة...#undef VALUE // لم يعد الرمز المميز 'VALUE' يتوسع من هنا...

وحدات ماكرو مُعرّفة مسبقًا

يُوفّر المُعالج المُسبق بعض تعريفات الماكرو تلقائيًا. يُحدّد معيار لغة C أن __FILE__يُوسّع إلى اسم الملف الذي تتم معالجته، __LINE__ويُوسّع إلى رقم السطر الذي يحتوي على التوجيه. يقوم الماكرو التالي، DEBUGPRINT، بتنسيق وطباعة رسالة تتضمن اسم الملف ورقم السطر.

#define DEBUGPRINT(_fmt, ...) printf("[%s:%d]: " _fmt, __FILE__, __LINE__, __VA_ARGS__)

بالنسبة لرمز المثال أدناه الموجود في السطر 30 من الملف util.c وللعد 123، يكون الناتج كالتالي: .[util.c:30]: count=123

DEBUGPRINT ( "count=%d \n " , count );

حدد معيار C الأول أن __STDC__قيمة `true` تتوسع إلى "1" إذا كان التنفيذ متوافقًا مع معيار ISO، وإلى "0" خلاف ذلك، وأن قيمة `false` __STDC_VERSION__تتوسع إلى قيمة عددية تحدد إصدار المعيار الذي يدعمه التنفيذ. تدعم مُجمِّعات C++ القياسية هذه __cplusplusالماكرو. يجب على المُجمِّعات التي تعمل في الوضع غير القياسي عدم تعيين هذه الماكرو، أو تعريف ماكرو أخرى للإشارة إلى الاختلافات.

تتضمن وحدات الماكرو القياسية الأخرى __DATE__التاريخ الحالي، والوقت __TIME__الحالي.

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

يُعرف أحد أنماط استخدام معالج C المسبق، والذي لا يحظى بشهرة واسعة، باسم وحدات الماكرو X. [ 9 ] [ 10 ] [ 11 ] وحدة الماكرو X هي ملف رأس . عادةً ما تستخدم هذه الملفات الامتداد .def بدلاً من الامتداد التقليدي .h . يحتوي هذا الملف على قائمة باستدعاءات ماكرو متشابهة، والتي يمكن الإشارة إليها باسم "وحدات الماكرو المكونة". ثم تتم الإشارة إلى ملف التضمين بشكل متكرر.

تُعرّف العديد من المترجمات وحدات ماكرو إضافية غير قياسية. ومن المراجع الشائعة لهذه الوحدات مشروع Pre-defined C/C++ Compiler Macros ، الذي يسرد "وحدات ماكرو مُعرّفة مسبقًا للمترجم يمكن استخدامها لتحديد المعايير والمترجمات وأنظمة التشغيل وبنى الأجهزة وحتى مكتبات وقت التشغيل الأساسية في وقت الترجمة".

تُعرّف معظم المترجمات التي تستهدف نظام التشغيل مايكروسوفت ويندوز الماكرو _WIN32. [ 12 ] يسمح هذا بتجميع التعليمات البرمجية، بما في ذلك أوامر المعالج المسبق، فقط عند استهداف أنظمة ويندوز. تُعرّف بعض المترجمات الماكرو WIN32بدلاً من ذلك. بالنسبة لهذه المترجمات التي لا تُعرّف _WIN32الماكرو ضمنيًا، يمكن تحديده في سطر أوامر المترجم باستخدام -D_WIN32.

#ifdef __unix__ // عادةً ما يتم تعريف __unix__ بواسطة المترجمات التي تستهدف أنظمة يونكس #include <unistd.h> #elifdef _WIN32 // عادةً ما يتم تعريف _WIN32 بواسطة المترجمات التي تستهدف أنظمة ويندوز 32 أو 64 بت #include <windows.h> #endif#ifdef __linux__ #define CURRENT_PLATFORM "Linux" #elifdef __APPLE__ #define CURRENT_PLATFORM "Apple" #elifdef _WIN32 #define CURRENT_PLATFORM "Windows" #else #define CURRENT_PLATFORM "Other" #endif

يختبر مثال الكود ما إذا كان هناك تعريف لماكرو . إذا كان موجودًا، يتم تضمين __unix__الملف . وإلا، فإنه يختبر ما إذا كان هناك تعريف لماكرو آخر. إذا كان موجودًا، يتم تضمين الملف أيضًا. وبالمثل، اعتمادًا على نوع الماكرو المُعرَّف، يتم تعريفه كسلسلة نصية تحتوي على اسم منصة النظام.<unistd.h>_WIN32<windows.h>CURRENT_PLATFORM

التحكم في الخط

يمكن ضبط قيم وحدات الماكرو المُعرّفة مسبقًا __FILE__لسطر __LINE__لاحق عبر #lineالتوجيه. في الكود أدناه، __LINE__يتم توسيعها إلى 314 وإلى __FILE__" pi.c ".

#line 314 "pi.c" printf ( "line=%d file=%s \n " , __LINE__ , __FILE__ );

المشغلون

يستطيع المعالج المسبق تفسير المعاملات وتقييم التعبيرات الأساسية جدًا، مثل الثوابت العددية، والمعاملات الحسابية، ومعاملات المقارنة، والمعاملات المنطقية، وعمليات البت، ومعامل التحويل، definedومعامل #تحويل السلسلة. وهذا يسمح للمعالج المسبق بإجراء عمليات تقييم مثل:

// إذا كانت قيمة X تساوي 10، فإن المعالج المسبق يرى #if 10 == 10 #if X == 10

عامل محدد

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

الطريقتان التاليتان هما طريقتان مقبولتان لاستدعاء definedالمعامل.

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

عامل تحويل الرموز إلى سلاسل

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

تعريف STR(s)

STR(\n)يتوسع إلى "\n"ويتوسع إلى .STR(p="foo\n";)"p = \"foo\\n\";"

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

# تعريف XSTR(s) STR(s) # تعريف STR(s) #s # تعريف FOO 4

STR(FOO)يتوسع إلى "FOO"ويتوسع إلى .XSTR(FOO)"4"

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

تسلسل الرموز

يقوم عامل لصق الرموز ، الذي يُرمز له بـ ##، بدمج رمزين في رمز واحد. على سبيل المثال، يمكن استخدام هذا لتبسيط الشيفرة النمطية عن طريق إنشاء دوال جلب وتعيين ، على غرار تلك الموجودة في مشروع لومبوك (في جافا ). [ 13 ]

#define GETTER(type, method, name) \  type get##method() const noexcept { \  return this->name; \  }#define SETTER(type, method, name) \  void set##method(type value) noexcept { \  this->name = value; \  }باستخدام std :: string ;class Student { private : string name ; int age ; double gpa ; public : // constructors...دالة جلب ( سلسلة نصية ، الاسم ، الاسم ) دالة تعيين ( سلسلة نصية ، الاسم ، الاسم ) دالة جلب ( عدد صحيح ، العمر ، العمر ) دالة تعيين ( عدد صحيح ، العمر ، العمر ) دالة جلب ( عدد عشري ، المعدل التراكمي ، المعدل التراكمي ) دالة تعيين ( عدد عشري ، المعدل التراكمي ، المعدل التراكمي )// المزيد من الطرق... };

إجهاض

يمكن إيقاف المعالجة عبر #errorالتوجيه. على سبيل المثال:

#if RUBY_VERSION == 190 #error إصدار Ruby 1.9.0 غير مدعوم #endif

تحذير

ابتداءً من C23 [ 14 ] و C++23 [ 15 ] ، تم توفير توجيه تحذيري #warningلطباعة رسالة دون إيقاف البرنامج. من الاستخدامات الشائعة لهذا التوجيه التحذير من استخدام وظائف مهملة . على سبيل المثال:

قبل إصداري C23 وC++23، كان هذا التوجيه موجودًا في العديد من المترجمات كميزة غير قياسية، مثل مترجمات لغة C من GNU وIntel وMicrosoft وIBM. ولأنه كان غير قياسي، فقد اتخذ ماكرو التحذير أشكالًا مختلفة.

// تحذير من جنو، إنتل، وآي بي إم : "لا تستخدم ABC، فهو برنامج قديم. استخدم XYZ بدلاً منه."// Microsoft #pragma message("لا تستخدم ABC، فقد تم إيقاف استخدامها. استخدم XYZ بدلاً منها.")

ميزات غير قياسية

#pragma

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

قدّمت لغة C99 بعض التوجيهات القياسية، التي تأخذ الشكل #pragma STDC ...، والتي تُستخدم للتحكم في تنفيذ الفاصلة العائمة. _Pragma(...)كما أُضيف الشكل البديل الشبيه بالماكرو.

يُعدّ استخدام #pragma once أحد أكثر استخدامات هذا #pragmaالتوجيه شيوعًا ، حيث يعمل بنفس طريقة حماية #include ، مُختصرًا في توجيه واحد يُوضع في بداية الملف. ورغم كونه غير قياسي، إلا أنه مدعوم من قِبل معظم المُترجمات.

ثلاثيات

العديد من التطبيقات لا تدعم الرسوم البيانية الثلاثية أو لا تستبدلها افتراضياً.

التأكيد

وفرت بعض معالجات يونكس المسبقة ميزة التأكيد - والتي لا تشبه تأكيدات المكتبة القياسية إلا قليلاً. [ 16 ]

#include_next

يوفر GCC #include_nextإمكانية ربط رؤوس الملفات التي تحمل نفس الاسم. [ 17 ]

على سبيل المثال، إذا تم استبدال الملف <stdio.h>، فإن محاولة تضمين المكتبة القياسية <stdio.h>ستؤدي إلى تكرار لا نهائي للتضمين عند استخدام #include، حيث سيتم إعادة تضمين نفسها. #include_nextيحل هذا المشكلة عن طريق تضمين الملف التالي <stdio.h>الذي تم العثور عليه.

// override_stdio/stdio.h #ifndef MY_STDIO_H #define MY_STDIO_H// تجاوزات مخصصة #define printf(...) my_custom_printf(__VA_ARGS__)// تضمين ملف stdio.h التالي في مسار البحث #include_next <stdio.h> #endif

#import

على عكس لغتي C و C++، تتضمن لغة Objective-C #importتوجيهًا مشابهًا لتوجيه #includeتضمين الملفات، ولكنه يؤدي إلى تضمين الملف مرة واحدة فقط ، مما يلغي الحاجة إلى استخدام حواجز التضمين #pragma once. وهو جزء أساسي من لغة Objective-C.

#import <Foundation/Foundation.h> #import "MyClass.h"

في مايكروسوفت فيجوال سي++ (MSVC)، يوجد أيضًا #importتوجيه للمعالجة المسبقة، يُستخدم لاستيراد مكتبات الأنواع. [ 18 ] وهو توجيه غير قياسي.

#import "C:\\Program Files\\Common Files\\System\\ado\\msado15.dll" no_namespace rename("EOF", "ADOEOF")

لا ينبغي الخلط بين هذه الكلمات والكلمة المفتاحية C++ import، والتي تستخدم لاستيراد وحدات C++ (منذ C++20 )، وليست توجيهًا للمعالج المسبق.

توجيه فارغ

يُعدّ التوجيه الفارغ، الذي يتكون من #الحرف فقط، وحده على سطر واحد، توجيهًا غير قياسي في Microsoft Visual C++. وليس له أي تأثير. [ 19 ]

#nullable

تُستخدم هذه #nullableالخاصية في لغة C# لتمكين أنواع المراجع القابلة للتصفير وتعطيلها. لتمكينها، استخدم `<T>` #nullable enable، ولتعطيلها، استخدم #nullable disable`<T>`.

# تمكين قابل للتصفيرstring? name = null ; // صحيح string fullName = null ; // تحذير: احتمال تعيين قيمة فارغةتعطيل قابل للتصفيرstring test = null ; // لا يوجد تحذير

هذا التوجيه غير موجود في لغة C/C++.

#region

تُستخدم التوجيهات #region`--` و` #endregion--` في لغة C# لتوسيع/طي أجزاء من التعليمات البرمجية في بيئات التطوير المتكاملة (IDEs)، ولا تؤثر على عملية تجميع البرنامج الفعلية. يُستخدم هذا التوجيه بشكل أساسي لتنظيم التعليمات البرمجية وتحسين قابليتها للقراءة.

باستخدام النظام ؛طرق مساعدة المنطقةvoid Log ( string message ) { Console . WriteLine ( message ); }منطقة الإيداع

على الرغم من أن هذا التوجيه غير موجود في لغة C/C++، إلا أن MSVC وVisual Studio يحتويان عليه #pragma region. #pragma endregion[ 20 ] وبالتالي ، سيكون كود C++ المكافئ كما يلي:

باستخدام std :: string_view ؛#pragma region طرق مساعدةvoid log ( string_view message ) { std :: println ( message ); }منطقة نهاية براغما الجديدة

#using

تحتوي لغة C++/CLI#using على التوجيه، الذي يُستخدم لاستيراد البيانات الوصفية إلى برنامج من ملف لغة وسيطة من مايكروسوفت (مثل ملف .dll ). [ 21 ]

# باستخدام < MyComponent.dll > باستخدام " AssemblyA.dll " باستخدام " AssemblyB.dll "باستخدام مساحة الاسم System ؛public ref class B { public void Test ( A a ) { // ... } };int main ( array < String ^>^ args ) { A a ; B b ; B . Test ( a ); }

استخدامات أخرى

تقليديًا، كان معالج C المسبق أداة تطوير منفصلة عن المُصرّف الذي يُستخدم معه عادةً. في هذه الحالة، يُمكن استخدامه بشكل منفصل عن المُصرّف. ومن الأمثلة البارزة استخدامه مع نظام imake (الذي أصبح قديمًا) ولمعالجة Fortran المسبقة . مع ذلك، فإن استخدامه كمعالج مسبق للأغراض العامة محدود، إذ يجب أن تكون لغة الشيفرة المصدرية شبيهة بلغة C نسبيًا حتى يتمكن المعالج المسبق من تحليلها. [ 2 ]

يقوم مُصرّف GNU Fortran بتشغيل المعالج المسبق في "الوضع التقليدي" قبل تجميع كود Fortran في حال استخدام امتدادات ملفات معينة. [ 22 ] تُقدّم Intel معالجًا مسبقًا لـ Fortran، يُسمى fpp، للاستخدام مع مُصرّف ifort ، والذي يتمتع بقدرات مماثلة. [ 23 ]

يعمل معالج C المسبق بشكل مقبول مع معظم لغات التجميع واللغات الشبيهة بلغة Algol. يتطلب ذلك عدم تعارض بنية اللغة مع بنية معالج C المسبق، ما يعني عدم وجود أسطر تبدأ بـ `<`، #وأن علامات الاقتباس المزدوجة، التي يفسرها معالج C المسبق على أنها سلاسل نصية حرفية ويتجاهلها بالتالي، ليس لها أي معنى نحوي آخر. يُعد "الوضع التقليدي" (الذي يعمل كمعالج C مسبق قبل معيار ISO) أكثر تساهلاً وأنسب لهذا الاستخدام. [ 24 ]

توفر بعض المترجمات الحديثة مثل مترجم GNU C المعالجة المسبقة كميزة من ميزات المترجم؛ وليس كأداة منفصلة.

القيود

قيود استبدال النص

يُعد استبدال النصوص ذا مخاطر عالية نسبياً للتسبب في أخطاء برمجية مقارنةً ببنى البرمجة الأخرى. [ 25 ] [ 26 ]

على وجه الخصوص، تفتقر وحدات المعالجة المسبقة للغة C إلى الوعي النحوي وتفشل في الحفاظ على البنية المعجمية، على عكس وحدات المعالجة النحوية الموجودة في لغات مثل Lisp أو Scala [ 27 ] أو Rust [ 28 ] .

تقييم متعدد مخفي

لنأخذ في الاعتبار التعريف الشائع للماكرو MAX:

#define MAX(a, b) (((a) > (b)) ? (a) : (b))

يتم تقييم التعبيرين المُمثلين بـ aو bمرتين بسبب توسيع الماكرو، لكن هذا الجانب غير واضح في الكود الذي يُشار فيه إلى الماكرو. إذا كانت قيمة التعبيرين ثابتة، فإن التقييم المتعدد لا يُشكل مشكلة من الناحية المنطقية، على الرغم من أنه قد يؤثر على أداء وقت التشغيل. ولكن إذا تم تقييم تعبير ما إلى قيمة مختلفة في التقييم اللاحق، فقد تكون النتيجة غير متوقعة. على سبيل المثال، إذا كان ، فإن نتيجة هي 2. إذا تم تقييم و مرة واحدة فقط، فستكون نتيجة هي نفسها، ولكن مع التقييم المزدوج، تكون النتيجة 3.inti=1,j=2;MAX(i,j)abMAX(i++,j++)

ترتيب العمليات الخفي

قد يؤدي عدم وضع الوسائط بين قوسين إلى نتائج غير متوقعة. على سبيل المثال، يمكن كتابة ماكرو لمضاعفة قيمة ما على النحو التالي:

#define DOUBLE(x) 2 * x

لكن يتم توسيعها إلى ، والتي بسبب ترتيب العمليات يتم تقييمها إلى 4 بينما القيمة المتوقعة هي 6. للتخفيف من هذه المشكلة، يجب أن يحتوي الماكرو على جميع التعبيرات ومتغيرات الاستبدال بين قوسين:DOUBLE(1+2)2*1+2

#define DOUBLE(x) (2 * (x))

ليس للأغراض العامة

معالج C المسبق ليس كاملاً من حيث قدرة تورينج ، ولكنه يقترب من ذلك. يمكن تحديد العمليات الحسابية التكرارية، ولكن بحد أقصى ثابت لمقدار التكرار المُنفَّذ. [ 29 ] ومع ذلك، فإن معالج C المسبق ليس مصممًا ليكون لغة برمجة عامة الأغراض، ولا يؤدي أداءً جيدًا كلغة برمجة عامة الأغراض. نظرًا لأن معالج C المسبق لا يمتلك ميزات بعض المعالجات المسبقة الأخرى، مثل وحدات الماكرو التكرارية، والتوسيع الانتقائي وفقًا للاقتباس، وتقييم السلاسل النصية في الشروط، فإنه محدود للغاية مقارنةً بمعالج ماكرو أكثر عمومية مثل m4 .

التخلص التدريجي

نظراً لقيودها وافتقارها إلى سلامة النوع (حيث أن المعالج المسبق غافل تماماً عن قواعد اللغة C/C++، ويقوم فقط باستبدال النصوص)، تمت إضافة ميزات لغة C و C++ على مر السنين لتقليل قيمة المعالج المسبق والحاجة إليه.

ثابت

لفترة طويلة، كان استخدام ماكرو المعالج المسبق هو الطريقة المُفضلة لتعريف قيمة ثابتة. وكان البديل دائمًا هو تعريف متغير، لكن ذلك يؤدي إلى استهلاك ذاكرة وقت التشغيل. يسمح constتركيب لغوي أحدث (منذ C++11 وC23) بتعريف قيمة ثابتة في وقت الترجمة لا تستهلك ذاكرة وقت التشغيل. [ 30 ]constexpr

// مع المعالج المسبق: #define MAX_NUMBER_OF_RETRIES 3 #define EARTH_GRAVITATIONAL_ACCELERATION 9.8// باستخدام constexpr: constexpr int MAX_NUMBER_OF_RETRIES = 3 ; constexpr double EARTH_GRAVITATIONAL_ACCELERATION = 9.8 ;

دالة مضمنة

لفترة طويلة، كان استخدام الماكرو الشبيه بالدالة هو الطريقة الوحيدة لتعريف سلوك شبيه بالدالة دون زيادة في تكلفة استدعاء الدالة أثناء التشغيل. وبفضل inlineالكلمة المفتاحية والمترجمات المُحسِّنة التي تُضمِّن الكود تلقائيًا، يُمكن استدعاء بعض الدوال دون زيادة في تكلفة الاستدعاء.

// مع المعالج المسبق: #define MAX(a, b) ((a) > (b) ? (a) : (b))// مع تضمين: inline constexpr auto max ( auto a , auto b ) noexcept { return ( a > b ) ? a : b ; }

يستورد

يُقيّد توجيه التضمين بنية الكود، إذ يسمح فقط بتضمين محتوى ملف واحد في ملف آخر. تدعم اللغات الحديثة مفهوم الوحدات النمطية التي تحتوي على رموز عامة تستوردها الوحدات النمطية الأخرى ، بدلاً من تضمين محتوى الملفات. يرى الكثيرون أن الكود الناتج يتميز بتقليل الشيفرة النمطية وسهولة الصيانة، حيث يوجد ملف واحد فقط لكل وحدة نمطية، بدلاً من ملف رأس وجسم. أضافت لغة C++20 وحدات نمطية ، وعبارة importلا تتم معالجتها مسبقًا. [ 31 ] [ 32 ] تُترجم الوحدات النمطية في C++ وتُربط بشكل أسرع من ملفات الرأس التقليدية، [ 33 ] وتُغني عن الحاجة إلى حراس #include أو #pragma once . حتى C++26، كانت الكلمات المفتاحية تُعالج جزئيًا بواسطة المعالج المسبق.importexportmodule

// مع تضمين: #include "wikipedia/examples/utils.h"// مع الاستيراد: استيراد ويكيبيديا . أمثلة . أدوات ؛

بالنسبة لقواعد البيانات البرمجية التي لا يمكن نقلها إلى الوحدات النمطية مباشرةً، توفر لغة C++ ميزة "وحدات الرأس"، التي تسمح باستيراد ملفات الرأس بنفس طريقة استيراد الوحدات النمطية. على عكس الوحدات النمطية، يمكن لوحدات الرأس إصدار وحدات ماكرو، مما يقلل من التداخل أثناء عملية النقل. صُممت وحدات الرأس لتكون حلاً انتقالياً قبل الانتقال الكامل إلى الوحدات النمطية. [ 34 ] على سبيل المثال، يمكن كتابة `header` بدلاً من `header` ، أو ` header` بدلاً من `header` . ومن المفارقات أن معظم أنظمة البناء، مثل CMake ، لا تدعم هذه الميزة حالياً.import<string>;#include<string>import"MyHeader.hpp";#include"MyHeader.hpp"

في Clang ، يتم توفير ميزة وحدة غير قياسية للغة C ، مما يسمح باستيراد ملفات الرأس كوحدات. [ 35 ]

انظر أيضاً

مراجع

  1. معالجة مسبقة للنصوص للأغراض العامة باستخدام معالج C المسبق. يدعم JavaScript
  2. 1 2 "معالج لغة C المسبق: نظرة عامة" . تم الاطلاع عليه بتاريخ 17 يوليو 2016 .
  3. "توجيهات المعالج المسبق في لغة C#" . مايكروسوفت. 14 يناير 2022.
  4. "توجيهات المُصرّف" . learn.microsoft.com . مايكروسوفت ليرن. 30 يوليو 2025.
  5. ريتشي (1993)
  6. "بيل ساب - ساب مع وحدات ماكرو شرطية وتكرارية" . HOPL: الموسوعة التاريخية الإلكترونية للغات البرمجة . مؤرشف من الأصل في 14 أكتوبر 2023. تم الاطلاع عليه في 4 أكتوبر 2020 .
  7. "WG14-N3017 : #embed - آلية تضمين موارد ثنائية قابلة للمسح وسهلة الاستخدام في الأدوات" . open-std.org . 27 يونيو 2022. مؤرشف من الأصل في 24 ديسمبر 2022. 
  8. "#embed - آلية تضمين موارد ثنائية قابلة للمسح وسهلة الاستخدام للأدوات" . open-std.org .
  9. ويرزينيوس، لارس سي. "حيلة المعالج المسبق لتنفيذ أنواع البيانات المتشابهة". تم الاطلاع عليه في 9 يناير 2011
  10. مايرز، راندي (مايو 2001). "لغة C الجديدة: وحدات الماكرو X" . مجلة دكتور دوبز . تم الاطلاع عليه في 1 مايو 2008 .
  11. بيال، ستيفان (أغسطس 2004). "المغذيات الكبيرة الفائقة" . تم الاطلاع عليه بتاريخ 27 أكتوبر 2008 .{{cite journal}}يتطلب الاستشهاد بالمجلة ( مساعدة )|journal=
  12. قائمة بوحدات الماكرو المُعرّفة مسبقًا لتنفيذ ANSI C و Microsoft C++.
  13. مشروع لومبوك. "@Getter و@Setter" . projectlombok.org . مشروع لومبوك . تم الاطلاع عليه بتاريخ 22 أبريل 2026 .
  14. "WG14-N3096 : مسودة ISO/IEC 9899:2023" (ملف PDF) . open-std.org . 1 أبريل 2023. مؤرشف (ملف PDF) من النسخة الأصلية في 2 أبريل 2023. 
  15. "مسودة عمل، معيار للغة البرمجة C++" (PDF) . 22 مارس 2023.
  16. ميزات GCC القديمة
  17. "رؤوس التغليف (معالج C المسبق)" .
  18. "#import directive (C++)" . learn.microsoft.com.
  19. "التوجيه الفارغ" . learn.microsoft.com.
  20. "region and endregion pragma" .
  21. "#using directive (C++/CLI)" . learn.microsoft.com. 29 يونيو 2022.
  22. "1.3 المعالجة المسبقة والتجميع الشرطي" . مشروع جنو.
  23. "استخدام المعالج المسبق fpp" . إنتل . تم الاطلاع عليه بتاريخ 14 أكتوبر 2015 .
  24. "نظرة عامة (معالج C المسبق)" . gcc.gnu.org . مع ذلك، يمكنك غالبًا استخدام cpp مع تطبيقات ليست بلغة C. لغات البرمجة الأخرى الشبيهة بلغة Algol آمنة في الغالب (مثل Ada). وكذلك لغة التجميع، مع توخي الحذر. يحافظ وضع -traditional-cpp على مساحة بيضاء أكبر، وهو أكثر تساهلاً بشكل عام. يمكن تجنب العديد من المشاكل بكتابة تعليقات بأسلوب C أو C++ بدلاً من تعليقات اللغة الأصلية، والحفاظ على بساطة وحدات الماكرو.
  25. جيرارد ج. هولزمان. "قوة العشرة - قواعد لتطوير التعليمات البرمجية الحساسة للسلامة" (ملف PDF) . سلامة وحدات الماكرو . ص 4. 
  26. مايكل د. إرنست؛ جريج ج. بادروس ؛ ديفيد نوتكين (ديسمبر 2002). "تحليل تجريبي لاستخدام معالج C المسبق" . معاملات IEEE في هندسة البرمجيات . 28 (12): 1146-1170 . Bibcode : 2002ITSEn..28.1146E . doi : 10.1109/TSE.2002.1158288 .
  27. "Def Macros" . وثائق سكالا . تم الاطلاع عليه في 5 أبريل 2021 .
  28. "الماكرو - لغة برمجة رست" . doc.rust-lang.org . تم الاطلاع عليه بتاريخ 5 أبريل 2021 .
  29. "هل المعالج المسبق C99 كامل تورينج؟" . مؤرشف من الأصل في 24 أبريل 2016.
  30. غابرييل دوس ريس؛ بيارن ستروستروب (22 مارس 2010). "تعبيرات الثوابت العامة للغات برمجة الأنظمة، وقائع مؤتمر SAC '10" (ملف PDF) . مؤرشف (ملف PDF) من الأصل في 13 يونيو 2018. تم الاطلاع عليه في 8 يوليو 2024 .
  31. "N4720: مسودة عمل، إضافات إلى لغة C++ للوحدات النمطية" (ملف PDF) . مؤرشف (ملف PDF) من الأصل في 30 أبريل 2019.
  32. "P1857R1 – اكتشاف تبعيات الوحدات النمطية" .
  33. "نظرة عامة على الوحدات في لغة C++" . مايكروسوفت. 12 فبراير 2022.
  34. "شرح تفصيلي: إنشاء واستيراد وحدات الرأس في Microsoft Visual C++" . مايكروسوفت. 12 أبريل 2022.
  35. "الوحدات" . clang.llvm.org .

مصادر