سلسلة ++c للمحترفين كيفية تصريف وبناء البرامج المكتوبة بلغة Cpp


محمد الميداوي

ينبغي تصريف البرامج المكتوبة بلغة C++‎ قبل أن تتمكن تلك البرامج من العمل، وستجد مجموعة كبيرة ومتنوّعة من برامج التصريف أو المصرِّفات (compilers) المتاحة والمناسبة لنظام التشغيل الذي تعمل به.

التصريف بواسطة GCC

التصريف دون تحسين مفيد في المراحل الأولى من التطوير والتنقيح، على الرغم من أنّ خيار ‎-Og‎ موصىً به في الإصدارات الحديثة من GCC، وبناء عليه فيمكن تصريف وربط ملف تنفيذي وغير محسَّن على افتراض أن هناك ملفًا مصدريًا واحدًا يسمى main.cpp، وذلك على النحو التالي:

g++ - o app - Wall main.cpp - O0

ولإنتاج ملف مُحسّن قابل للتنفيذ لاستخدامه في الإنتاج، استخدم أحد خيارات ‎-O‎ (راجع: ‎،-O1‎، ‎-O2‎، ‎-O3‎، ‎-Os‎، ‎-‎‎Ofast‎):

g++ - o app - Wall - O2 main.cpp

في حال حذف الخيار ‎-O، سيُستخدَم ‎-O0 الذي يعني إلغاء التحسينات، كخيار افتراضي، واعلم أن تحديد ‎-O بدون رقم يجعله مساويًا لـ ‎-O1.

وكخيار بديل، يمكن أن نستخدم رايات التحسين (optimization flags) من مجموعات ‎O‎ -أو تحسينات تجريبية أخرى- مباشرةً. انظر المثال التالي حيث يبني البرنامج مع التحسين ‎-O2‎، بالإضافة إلى راية واحدة من مستوى التحسين ‎-O3‎:

g++ -o app -Wall -O2 -ftree-partial-pre main.cpp

لإنتاج ملفّ تنفيذي مُحسّن لأجل منصة معيّنة -لاستخدامه على جهاز له نفس المعمارية-، استخدم:

g++ -o app -Wall -O2 -march=native main.cpp

سوف ينتُج عن الشيفرَتين أعلاه ملفٌّ ثنائي (binary file) يمكن تشغيله باستخدام ‎.\app.exe‎ على Windows، أو باستخدام ‎./app‎ على Linux و Mac OS، كما يمكن أيضًا تخطي الراية ‎-o‎، وفي هذه الحالة سينشئ GCC الملفّ التنفيذي الافتراضي ‎a.exe‎ على Windows و ‎a.out‎ على الأنظمة الشبيهة بيونكس (Unix-like).

لأجل تصريف ملف دون ربطه، استخدم الخيار ‎-c‎:

g++ -o file.o -Wall -c file.cpp

سينتج عن هذا ملفٌّ يحمل الاسم ‎file.o‎، والذي يمكن ربطه لاحقًا بملفّات أخرى لإنتاج ملف ثنائي:

g++ -o app file.o otherfile.o

انظر gcc.gnu.org للمزيد من التفاصيل حول خيارات التحسين، خاصة ‎-Og‎، وهو التحسين مع التركيز على تجربة التنقيح (debugging)، الذي يوصى باستخدامه في دورات تحرير-تصريف-تنقيح القياسية - standard edit-compile-debug cycle)، وكذلك ‎-Ofast‎ الذي يشمل جميع التحسينات، بما في ذلك تلك التي تتجاهل الامتثال الصارم للمعايير.

تُمكِّنك راية ‎-Wall‎ من إطلاق تحذيرات من بعض الأخطاء الشائعة، وينبغي أن تُستخدم دائما، ويوصى باستخدام ‎-Wextra‎ لتحسين جودة الشيفرة، وكذلك رايات التحذير الأخرى التي لا تُمكَّن تلقائيًا من قِبل ‎-Wall‎ و ‎-‎‎Wextra‎.

إذا كانت الشيفرة تتوقع معيارًا محدّدًا للغة C++‎، فيمكنك تحديد المعيار الذي تريد استخدامه عن طريق تضمين راية ‎-std=‎. وتتوافق القيم المدعومة مع سنة الإصدار النهائي لمعيار ISO C++‎، واعتبارا من GCC 6.1.0 فإنّ القيم الصالحة لـ ‎std=‎ هي: ‎c++98‎ / ‎c++03‎، ‎c++11‎، ‎c++14‎، و‎c++17‎ / ‎c++1z‎. لاحظ أن القيم المفصولة بشرطةٍ مائلة / متكافئة.

g++ -std=c++11 <file>

يتضمّن GCC بعض الإضافات (Extensions) (extensions) الخاصة بالمُصرِّف، والتي تُعطَّل عندما تتعارض مع معيار قياسي محدّد من قبل راية ‎-std=‎، فإذا أردت التصريف مع كل الإضافات (Extensions) المُمَكَّنة، فاستخدام القيمة ‎gnu++XX‎، حيث تمثّل ‎XX‎ أيًّا من السنوات المذكورة أعلاه.

ويُستخدَم المعيار الافتراضي في حالة عدم تعريف أيّ منها، وبالنسبة لإصدارات GCC التي تسبق 6.1.0 فإنّ الإعداد الافتراضي هو -‎std‎ = ‎gnu‎++03، أما في GCC 6.1.0 والإصدارات الأحدث فإن الإعداد الافتراضي هو ‎-std=gnu++14‎.

لاحظ أنّه نظرا لوجود بعض الأخطاء (bugs) في GCC، فيجب أن تكون راية pthread- حاضرة في التصريف والربط إن أردت أن يدعم GCC معيار الخيوط (threading) الذي أُدخل في C++‎11، مثل ‎std::thread‎ و ‎std::wait_for‎. قد لا ينتج عن حذفها عند استخدام الخيوط أيّ تحذيرات، ولكن قد تحصل على نتائج غير صحيحة في بعض المنصّات.

الربط بالمكتبات Linking with libraries

استخدم خيار ‎-l‎ لتمرير اسم المكتبة:

g++ main.cpp -lpcre2-8
# 8bit code units (UTF-8) في وحدات الشيفرات ثمانية البتّات PCRE2 هي مكتبة pcre2-8 

إذا لم تكن المكتبة موجودة في مسار المكتبة القياسية، فأضف المسار باستخدام ‎-L‎:

g++ main.cpp -L/my/custom/path/ -lmylib

يمكن ربط عدّة مكتبات معًا:

g++ main.cpp -lmylib1 -lmylib2 -lmylib3

إذا كانت إحدى المكتبات تعتمد على مكتبة أخرى، فضع المكتبة المعتِمدة قبل المكتبة المستقلة:

g++ main.cpp -lchild-lib -lbase-lib

أو اترك الرابط (linker) يتكفّل بتحديد الترتيب عبر الخيارات ‎--start-group‎ و ‎--end-group‎ (ملاحظة هذا له تكلفة كبيرة على الأداء):

g++ main.cpp -Wl,--start-group -lbase-lib -lchild-lib -Wl,--end-group

التصريف باستخدام Visual Studio (واجهة رسومية)

  1. نزِّل Visual Studio Community 2015 وثبِّته.
  2. افتح Visual Studio Community .
  3. انقر على File، ثم NewK ثم Project.

bFNzb.png

  1. انقر على Templates ثم ++Visual C ثم Win32 Console Application، ثم ضع اسمًا للمشروع وليكن MyFirstProgram.

kYTy1.png

  1. انقر على Ok.
  2. انقر على "Next " في النافذة التالية.

Rebpz.png

  1. اختر الخانة ‎Empty project‎ تحت الخيارات الإضافية (Additional options) ثم انقر على "Finish":

P0P5J.png

  1. انقر بالزر الأيمن فوق مجلد ملف المصدر ثم ‎Add، ثم New Item:

DLwEd.png

  1. حدّد ملف C++‎ وقم بتسمية main.cpp، ثم انقر فوق Add:

zQaws.png

  1. انسخ والصق الشيفرة التالية في الملف الجديد main.cpp:
#include <iostream>

int main() {
    std::cout << "Hello World!\n";
    return 0;
}

ينبغي أن تبدو بيئة العمل لديك كما يلي:

vTBkv.png

  1. انقر على Debug ثم Start Without Debugging، (أو اضغط على ctrl + F5):

B3twO.png

  1. يجب أن تحصل على الخرج التالي في سطر الأوامر:

1AwnS.png

مُصرِّفات الشبكة Online Compilers

توفّر العديد من المواقع مصرّفات C++‎ يمكن الوصول إليها عبر شبكة الإنترنت، وتختلف مزايا وإمكانيات مصرّفات الشبكة من موقع إلى آخر، ولكنها عادة ما تسمح بالقيام بما يلي:

  • لصق الشيفرة في نموذج في المتصفّح.
  • تحديد بعض خيارات المصرّف، وتصريف الشيفرة.
  • الحصول على خرج المصرّف و/أو البرنامج.

عادةً ما تكون المُصرِّفات الشبكية مقيّدة إذ أنّها تتيح لأيّ شخص تشغيل المصرّف وتنفيذ شيفرات عشوائية على الخادم، وهذا قد يكون مصدر خطر في حال تنفيذ شيفرات ضارة.

قد تكون مصرّفات الشبكة مفيدة للأغراض التالية:

  • تشغيل مقتطف صغير من شيفرة جهاز يفتقر إلى مصرّف C++‎ (مثل الهواتف الذكية والأجهزة اللوحية وما إلى ذلك).
  • التحقّق من أنّ الشيفرة تُصرَّف بنجاح في مختلف المُصرِّفات، وتعمل بنفس الطريقة بغض النظر عن المُصرِّف الذي صُرِّفت فيه.
  • تعلُّم أو تعليم أساسيات C++‎.
  • تعلّم ميزات C++‎ الحديثة (C++‎ 14 و C++‎ 17 في المستقبل القريب) إذا لم يكن لديك مصرّف C++‎ حديث على جهازك.
  • رصد الأخطاء التي يمكن أن تكون موجودة في المصرّف الذي تعمل به بمقارنته بمجموعة كبيرة من المصرّفات الأخرى.
  • التحقق ممّا إذا كانت الإصدارات اللاحقة من المصرّف الذي تعمل قد صُحِّحت في حال لم تتوفر تلك الإصدارات على جهازك.
  • حل المشاكل والتمارين عبر الشبكة.

بالمقابل، لا ينبغي استخدام مُصرِّفات الشبكة لأجل:

  • تطوير برامج كاملة (ولو كانت صغيرة) باستخدام C++‎، وفي العادةً فإن مصرّفات الشبكة لا تسمح بالارتباط مع مكتبات خارجية، أو تنزيل أدوات البناء.

  • إجراء حسابات مكثفة. موارد الخادم الحسابية محدودة، لذلك سيوقَف أيّ برنامج يقدّمه المستخدم بعد بضع ثوانٍ من بدء تنفيذه، فوقت التنفيذ المسموح به يكفي عادة للاختبار والتعلم فقط.

  • قرصنة ومهاجمة الخادم الذي يعمل عليه المُصرِّف، أو أيّ جهة خارجية مُستضافة على الشبكة.

    أمثلة على المصرفات الشبكية:

  • codepad.org: مُصرِّف مع إمكانية مشاركة الشيفرة، وتحرير الشيفرة بعد التصريف، لكن التحذيرات و الأخطاء لا تعمل بشكل جيد.

  • coliru.stacked-crooked.com: مصرّف يمكّنك من أن تحدّد سطر الأوامر، ويتيح لك اختيار أحد المُصرِّفين GCC أو Clang.

  • cpp.sh: مُصرِّف يدعم C++‎ 14، لا يسمح لك بتحرير سطر أوامر المصرّف، ولكن يوفّر بعض الخيارات عبر عناصر التحكم في واجهة المستخدم الرسومية.

  • gcc.godbolt.org: يوفر قائمة واسعة من إصدارات المُصرِّف، والمعماريات، وهو مفيد للغاية لمن يحتاج إلى التحقّق من عمل الشيفرة في عدّة مُصرِّفات مختلفة. والمصرفات التالية هي بعض المصرّفات المتاحة: GCC و Clang و MSVC ومصرّف Intel و ELLCC و Zapcc، مع توفّر واحد أو أكثر من هذه المصرّفات للمعماريّات ARM و ARMv8 (مثلARM64) و Atmel AVR و MIPS و MIPS64 و MSP430 و PowerPC و x86 و x64. أيضًا، يمكن تعديل وسائط سطر الأوامر الخاص بالمُصرِّف.

  • ideone.com: يُستخدم على نطاق واسع لتوضيح سلوك الشيفرة، ويوفر كلًّا من GCC و Clang، لكنّه لا يسمح بتحرير سطر أوامر المصرّف.

  • melpon.org/wandbox: يدعم العديد من إصدارات Clang و GNU / GCC.

  • onlinegdb.com: بيئة تطوير متكاملة لكن محدودة، تتضمّن محررًا ومُصرِّفًا (gcc) ومنقّحًا (gdb).

  • rextester.com: يوفر المُصرِّفات Clang و GCC و Visual Studio لكل من C و C++‎ (إضافة إلى مُصرِّفات خاصّة بلغات أخرى)، ومكتبة Boost.

  • tutorialspoint.com/compile_cpp11_online.php: صدفة UNIX متكاملة، مع مصرّف GCC، ومستكشف مشاريع سهل الاستخدام.

  • webcompiler.cloudapp.net: مُصرِّف لبرنامج Visual Studio 2015، مُقدّم من قبل Microsoft كجزء من RiSE4fun.

التصريف باستخدام Visual C++‎ (سطر الأوامر)

بالنسبة للمبرمجين الذين اعتادوا على العمل بالمصرّفَين GCC أو Clang، والذين يودّون الانتقال إلى Visual Studio، أو المبرمجين الذين يفضّلون العمل بسطر الأوامر بشكل عام، يمكن استخدام مصرّف Visual C++‎ من سطر الأوامر إلى جانب بيئة تطوير متكاملة (IDE).

وإذا كنت ترغب في تصريف شيفرتك من سطر الأوامر في Visual Studio، فسيكون عليك إعداد بيئة سطر الأوامر، عن طريق فتح:

 Visual Studio Command Prompt/Developer Command Prompt/x86 Native Tools Command Prompt/x64 Native Tools Command Prompt

أو شيئٍا من هذا القبيل (يختلف الأمر حسب إصدار Visual Studio الذي تعمل به)، أو في موجّه الأوامر، من خلال الانتقال إلى المجلّد الفرعي ‎VC‎ في مجلّد التثبيت الخاصّ بالمصرّف (يكون عادةً ‎\Program Files (x86)\Microsoft Visual Studio x\VC‎، حيث يمثّل ‎x‎ رقم الإصدار (مثل ‎10.0‎ لعام 2010، أو ‎14.0‎ لعام 2015)، ثمّ تنفيذ الملفّ ‎VCVARSALL‎ مع مُعامل سطر الأوامر المُحدّد هنا.

لاحظ أنّه على عكس GCC، فإنّ Visual Studio لا يوفّر واجهة للرابط (‎link.exe‎) عبر المصرّف (‎cl.exe‎)، ولكنّها توفّر الرابط (linker) كبرنامج منفصل، ويستدعيه االمُصرِّف عند الإنهاء.

ويمكن استخدام ‎cl.exe‎ و ‎link.exe‎ بشكل منفصل مع عدّة ملفّات وخيارات، أو إخبار ‎cl‎ بتمرير الملفّات والخيارات إلى ‎link‎ إذا كانت المهمّتان ستُنجزان معًا، وستُترجم خيارات الربط المُحدّدة لـ ‎cl‎ إلى خيارات خاصة بـ ‎link‎، وتُمرّر الملفات غير المُعالجة بواسطة ‎cl‎ مباشرة إلى الرابط ‎link‎.

انظر هذه الصفحة للمزيد عن وسائط ‎link‎.

لاحظ أنّ الوسائط التي تخصّ ‎cl‎ حسّاسة لحالة الأحرف، وذلك على خلاف وسائط ‎link‎، وأن بعض الأمثلة التالية تستخدم المتغيّر "current directory" الخاصّ بصدفة (shell) ويندوز، ‎%cd%‎، عند تحديد أسماء المسار المطلق (absolute path names)، ويُوسَّع هذا المتغيّر إلى مجلّد العمل الحالي (current working directory).

وفي سطر الأوامر، سيكون هو المجلّد الذي كنت تستخدمه عند تشغيل ‎cl‎، وهو محدَّد في موجّه الأوامر - command prompt - افتراضيًا (مثلًا، في موجّه الأوامر ‎C:\src>‎، فإنّ ‎%cd%‎ سيكون ‎C:\src\‎).

يمكنك تصريف وربط ملف تنفيذي غير مُحسَّن (unoptimised executable) بافتراض أنّ هناك ملفّا مصدريًا واحدًا يُسمّى ‎main.cpp‎ في المجلد الحالي، وهذا مفيد في مرحلة التطوير الأولي والتنقيح، عبر استخدام أيٍّ ممّا يلي:

cl main.cpp
// "main.obj" إنشاء ملف
// "main.obj" إجراء الربط مع
// "main.exe" إنشاء الملف القابل للتنفيذ

cl /Od main.cpp

في المثال السابق، سيتصرف cl /Od main.cpp كسابقه، لكن يكون "Od/" هو خيار "Optimisation: disabled"، ويكون الخيار الافتراضي عند عدم تحديد أي من خيارات "O/".

بافتراض أنّ هناك ملفًّا مصدريًا إضافيًا " niam.cpp " في نفس المجلّد، فستنشئ الشيفرة التالية الملفين "main.obj" و "niam.obj"، ثم يجري الربط معهما، ومن ثم ينشئ الملف التنفيذي "main.exe":

cl main.cpp niam.cpp

يمكنك أيضًا استخدام محارف البدل (wildcards)، ستنشئ الشيفرة التالية ملف كائن "main.obj" إضافة إلى ملف كائن لكل ملف cpp. في المجلد التالي: "%cd%\src" ثم تُجري الربط مع "main.obj" وكل ملف كائن مُنشأ، وستكون كل ملفات الكائن في المجلد الحالي، ثم تولِّد ملف main.exe.

cl main.cpp src\* .cpp

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

cl /o name main.cpp
// "name.exe" تولد ملف قابل للتنفيذ
cl /o folder\ main.cpp
// "%cd%\folder" في المجلد "main.exe" إنشاء ملف
cl /o folder\name main.cpp
// "%cd%\folder" في المجلد "main.exe" إنشاء ملف
cl /Fename main.cpp
// "/o name" مثل
cl /Fename main.cpp
// "/o folder\" مثل
cl /Fefolder\name main.cpp
//  "/o folder\name" مثل

يمرِّر كلٌّ من ‎/o‎ و ‎/Fe‎ معاملاتِهما (دعنا نسميها ‎o-param‎) إلى ‎link‎ على شكل ‎/OUT:o-param‎، مع إلحاق الامتداد المناسب (بشكل عام ‎.exe‎ أو ‎.dll‎) لتسمية ‎o-param‎ بما يُناسب، في حين أنّ لـ ‎/o‎ و ‎/Fe‎ نفس الوظيفية - على حدّ علمي - إلّا أنّ الأخير هو المفضل في Visual Studio.

لقد أصبحت ‎/o‎ مهمَلة، ويبدو أنّها تًقدّم بشكل أساسي للمبرمجين الذين اعتادوا العمل بالمصرّفَين GCC و Clang. لاحظ أنّه على الرغم من أنّ المسافة الفارغة بين ‎/o‎ واسم المجلد المحدّد اختيارية، إلا أنه لا يمكن وضع مسافة بيضاء بين ‎/Fe‎ واسم المجلد المحدّد.

بالمثل، يمكن إنشاء ملف تنفيذي مُحسّن (لاستخدامه في مرحلة الإنتاج) على النحو التالي: سنحسِّن حجم الملف التنفيذي في السطر الأول لكن قد يكون هذا على حساب سرعة التنفيذ، أما في السطر الثاني فيحسِّن سرعة البرنامج، لكن ربما يجعل هذا حجم الملف أكبر.

cl /O1 main.cpp
cl /O2 main.cpp

هنا ننشئ ملفات خاصة لتحسين البرنامج، مما يسمح لسطر الأوامر أن يأخذ كل وحدات الترجمة في حسبانه خلال عملية التحسين، وسيمرر خيار توليد شيفرة الرابط-الوقت (Link-Time Code Generation) أو LTCG/، إلى الرابط LINK، ليخبره باستدعاء سطر الأوامر خلال مرحلة الربط لإجراء تحسينات إضافية. وينبغي ربط كائن الملف المُنشأ مع LTCG/ في حال عدم إجراء الربط في ذلك الوقت، كما يمكن استخدامها مع خيارات التحسين الأخرى في سطر الأوامر، …

cl /GL main.cpp other.cpp

أخيرًا، لإنتاج ملف تنفيذي مُحسَّن لأجل منصّة معيّنة (لاستخدامه في الإنتاج على جهاز ذي معمارية معيّنة)، فعليك اختيار موجّه الأوامر المناسب، أو مُعامل ‎VCVARSALL‎ المناسب للمنصّة المُستهدفة. ويجب أن يرصُد الرابط (‎link‎) المنصّة المطلوبة من ملفّات الكائن؛ وإلّا، فعليك استخدام الخيار ‎/MACHINE‎ لتعريف المنصّة المُستهدفة بشكل صريح. مثال: إذا أردت التصريف للتنفيذ على معمارية 64 بت، وتعذر على الرابط LINK أي يرى المنصة المستهدفة:

cl main.cpp / link / machine: X64

سينتج أيًا مما سبق ملفًا تنفيذيًا يحمل الاسم المحدّد من قِبل ‎/o‎ أو ‎/Fe‎، وفي حالة عدم توفير أيّ منهما، سيحمل اسمًا مطابقًا للملف المصدري الأول أو ملفِّ الكائن المحدّد للمُصرِّف.

cl a.cpp b.cpp c.cpp
// "a.exe" إنشاء
cl d.obj a.cpp q.cpp
// "d.exe" إنشاء
cl y.lib n.cpp o.obj
// "n.exe" إنشاء
cl / o yo zp.obj pz.cpp
// "yo.exe" إنشاء

لتصريف الملفّات دون ربط:

cl / c main.cpp
// "main.obj" إنشاء ملف

تخبر الشيفرةُ السابقة سطرَ الأوامر ‎cl‎ بالخروج دون استدعاء ‎link‎، وتنتج ملفّ كائن يمكن ربطه لاحقًا بالملفّات الأخرى لإنتاج ملف ثنائي. في الشيفرة التالية، ينشئ السطر الأول ملف الكائن niam.obj، ويجري الربط مع niam.obj و main.obj، ثم ينشئ الملف التنفيذي main.exe. ويجري السطر الثاني ربطًا مع niam.obj و main.obj، ثم ينشئ الملف التنفيذي main.exe.

cl main.obj niam.cpp

link main.obj niam.obj

هناك مُعاملات أخرى مهمّة لسطر الأوامر، ومن المفيد جدًا معرفتها، منها ما يلي:

  • cl /EHsc main.cpp: يشير EHsc إلى أنه لن تُمسك إلا اعتراضات ++C القياسية، وأن دوال C الخارجية لن ترفع اعتراضات (exceptions). يوصى بهذا المعامل لمن يريد كتابة شيفرة محمولة ومتعددة المنصات.

  • cl /clr main.cpp: يشير clr/ إلى أن الشيفرة يجب أن تُصرَّف لاستخدام اللغة المشتركة لوقت التشغيل، وهي آلة وهمية خاصة بإطار عمل NET.، وتسمح باستخدام لغة C++/CLI الخاصة بميكروسوفت إضافة إلى سطر أوامر ++C، وتنشئ ملفًا تنفيذيًا يتطلب NET. ليعمل.

  • cl /Za main.cpp: يشير Za/ إلى وجوب تعطيل إضافات (Extensions) ميكروسوفت، وأن الشيفرة يجب أن تُصرَّف وفق مواصفات ISO للغة ++C حصرًا، وهذا ضروري لضمان محمولية البرنامج.

  • cl /Zi main.cpp: يولد Zi/ ملف قاعدة بيانات PDB للبرنامج من أجل استخدامه عند تنقيح برنامج ما دون التأثير على مواصفات التحسين، ويُمرَّر خيار DEBUG/ إلى الرابط LINK.

  • cl /LD main.cpp: يخبر LD/ سطرَ الأوامر أن يضبط LINK كي يولد ملف DLL بدلًا من ملف تنفيذي، وسينتج الرابط ملف DLL إضافة إلى ملفي EXP و LIB لاستخدامهما عند الربط. مرر ملف LIB المرتبط بملف DLL إلى سطر الأوامر أو الرابط عند تصريف تلك البرامج، وذلك لاستخدام الأخير في برامج أخرى.

  • cl main.cpp /link /LINKER_OPTION: يمرر link/ كل ما بعده إلى الرابط مباشرة دون تحليل. استبدل LINKER_OPTION/ بخيارات الرابط التي تريد.

بالنّسبة للذين لديهم خبرة في التعامل مع اليونكسات وأشباهها، و/أو GCC / Clang و ‎cl‎ و ‎link‎، وأدوات سطر أوامر Visual Studio الأخرى، يمكنهم أن يقبلوا المُعاملات المُحدّدة باستخدام الواصلة - (مثل ‎-c‎) بدلًا من الشرطة المائلة (مثل ‎/c‎). إضافة إلى ذلك، يتعرّف نظام ويندوز على الشرطة المائلة العكسية \ (backslash) أو الأمامية / (slash) ويعدّهما فواصل صالحة للمسارات، لذلك يمكن استخدام المسارات التي على نمط يونكس وما شابهه.

يسهّل هذا تحويل أسطر أوامر المصرّف من ‎g++‎ أو ‎clang++‎ إلى ‎cl‎، أو العكس، بالحد الأدنى من التغييرات.

g++ - o app src / main.cpp
cl - o app src / main.cpp

ستحتاج إلى البحث عن أوامر مكافئة في توثيق المصرّف الذي تستخدمه و/أو في مواقع توثيق أخرى، وذلك عند محاولة نقل سطور الأوامر التي تستخدم خيارات معقّدة لـ ‎g++‎ أو ‎clang++‎.

وإن احتجت إلى استخدام ميزات لغة معيّنة في شيفرتك، فيلزم استخدام إصدار محدّد من MSVC. ومن الممكن في Visual C++‎ 2015 التحديث 3 اختيار إصدار المعيار الذي تريد اعتماده عند التصريف عبر الراية ‎/std‎، والقيمُ الممكنةُ هي ‎/std:c++14‎ و ‎/std:c++latest‎ (ستتبعُهما ‎/std:c++17‎ قريبًا).

ملاحظة في الإصدارات القديمة من هذا المُصرِّف، كانت بعض رايات المزايا متوفرة، بيْد أنّها كانت في الغالب تُستخدم لمعاينة الميزات الجديدة.

التصريف باستخدام Clang

بما أن الواجهة الأمامية لمصرّف Clang مُصمّمة لتكون متوافقة مع GCC، فإنّ معظم البرامج التي يمكن تصريفها عبر GCC ستُصرَّف كذلك عند استبدال ‎clang++‎ بـ‎g++‎ في برامج البناء النصية (build scripts)، وفي حال عدم إعطاء ‎-std=version‎، فسيُستخدَم الإصدار gnu11.

يمكن لمستخدمي ويندوز الذين اعتادوا على MSVC استبدال ‎clang-cl.exe‎ بـ ‎cl.exe‎. واعلم أن clang يحاول بشكل افتراضي أن يكون متوافقًا مع أحدث إصدار مُثبّت من MSVC. ويمكن استخدام clang-cl عبر تغيير ‎Platform toolset‎ في خاصّيات المشروع، وذلك عند التصريف باستخدام visual studio.

وفي كلتا الحالتين، سيكون clang متوافقًا عبر واجهته الأمامية وحسب، لكنه سيحاول أيضًا بناء ملفات ثنائية متوافقة. كذلك يجب على مستخدمي clang-cl أن ينتبهوا إلى أنّ التوافق مع MSVC ليس كاملًا.

لاستخدام clang أو clang-cl، يمكن استخدام التثبيت الافتراضي على توزيعات محدّدة من Linux، أو تلك المُجمَّعة في بيئات التطوير المتكاملة - IDEs - (مثل XCode على Mac). أما بالنسبة للإصدارات الأخرى من هذا المُصرِّف، أو في المنصّات التي لم يُثبّت عليها، فيمكن تنزيله من صفحة التنزيل الرسمية.

إذا كنت تستخدم CMake لبناء شيفرتك، فيمكنك تبديل االمُصرِّف عادة عن طريق تعيين متغيّرَي البيئة ‎CC‎ و ‎CXX‎ على النحو التالي:

mkdir build
cd build
CC=clang CXX=clang++ cmake ..
cmake --build .

عملية التصريف في C++‎

بعد تطوير برنامج بلغة C++‎، فإنّ الخطوة التالية هي تصريف ذلك البرنامج قبل تشغيله. والتصريف (compiling) هو العملية التي تحوّل البرنامج المكتوب بلغة قابلة للقراءة البشرية، مثل C و C++‎ وغيرهما، إلى شيفرة الآلة، وهي شيفرة يمكن أن تُفهمها وحدة المعالجة المركزية مباشرة. على سبيل المثال، إذا كان لديك ملفّ مصدَري مكتوب بلغة C++‎ باسم prog.cpp، وقمت بتنفيذ الأمر compile …

g++ -Wall -ansi -o prog prog.cpp

ستحدث 4 مراحل رئيسية عند إنشاء ملف تنفيذي من الملف المصدري.

  1. يأخذ معالج C++‎ الأولي ملفّ الشيفرة المصدرية ويتعامل مع الترويسات (include#)، ووحدات الماكرو (‎#define‎) وموجّهات المعالج الأخرى.
  2. تُصرّف شيفرة C++‎ المصدرية المُوسّعة التي تم إنتاجها من قبل معالج C++‎ الأوّلي إلى لغة التجميع (assembly) المناسبة للمنصّة.
  3. تُصرَّف الشيفرة المجمّعة التي تم إنتاجها بواسطة المصرّف إلى كائن شيفرة (object code) مناسب للمنصة.
  4. يُربط ملفّ كائن الشيفرة المُنتج من قبل المجمّع مع ملفّات كائنات الشيفرة الخاصة بمكتبات الدوال المُستخدمة، وذلك لإنتاج مكتبة أو ملف قابل للتنفيذ.

المعالجة الأولية Preprocessing

يُعالج المعالج الأولي تعليمات المعالج، مثل ‎#include و ‎#define، وهو غير واعٍ بصيغة C++‎، لذا يجب توخّي الحذر عند استخدامه.

يعمل المعالج الأوّلي على ملفّ ++C مصدري واحد عن طريق استبدال محتوى الملفّات المناسبة (والتي تكون عادة مجرّد تصريحات) بتعليمات ‎#include، ويستبدل وحدات الماكرو (‎#define)، ويختار عدة أجزاء من النص بحسب تعليمات ‎#if و ‎#ifdef و ‎#fndef.

يعمل المعالج الأوّلي على مجموعة من وحدات المعالجة الأوّلية (preprocessing tokens)، ويُعُرِّف استبدال الماكرو على أنّه استبدال مقاطع بمقاطع أخرى (يتيح المُعامل ## دمجَ شيفرَتين إذا كان ذلك مناسبًا).

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

قد تنتج بعض الأخطاء في هذه المرحلة عند استخدام التعليمتين #if و ‎#error. ونستطيع إيقاف العملية في مرحلة المعالجة الأولية باستخدام راية المصرّف أدناه:

g++ -E prog.cpp

التصريف

تُجرى خطوة التصريف على كل خرج من مخرجات المعالج الأوّلي (Preprocessor)، ويحلّل االمُصرِّف شيفرة C++‎ المصدرية الخالصة - دون أي تعليمات للمعالج الأولي الآن- ثمّ يحوّلها إلى شيفرة المجمّع، ثم يستدعي الواجهة الخلفية الأساسية (underlying backend)، وهي المجمّع في سلسلة الأدوات، التي تجمّع تلك الشيفرة وتحوّلها إلى شيفرة الآلة، وتنتج ملفًّا ثنائيًا وفق تنسيق معيّن (مثل ELF أو COFF أو a.out …).

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

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

يمكن وضع ملفات الكائنات الناتجة في أرشيفات خاصة تسمّى المكتبات الساكنة (static libraries)، لتسهيل استخدامها لاحقًا، ويُبلَّغ في هذه المرحلة عن أخطاء المصرّف "المعتادة"، مثل أخطاء الصيغة، أو أخطاء فشل تحليل التحميل الزائد.

لإيقاف العملية بعد خطوة التصريف، يمكن استخدام الخيار ‎-S:

g++ -Wall -ansi -S prog.cpp

التجميع Assembling

ينشئ المجمّع شيفرة الكائن، وقد ترى على نظام UNIX ملفّات ذات لاحقة ‎.o (أو .OBJ على MSDOS) للدلالة إلى ملفات كائنات الشيفرَة (object code files).

في هذه المرحلة، يحوّل المجمّع تلك الملفّات من شيفرة مُجمَّعة (assembly code) إلى تعليمات على مستوى الآلة، كما سيكون الملفُّ المُنشأ كائنَ شيفرة قابلة للنقل (relocatable object code)، ومن ثم ستنشئ مرحلةُ التصريف برنامجَ الكائن القابل للنقل، ويمكن استخدام هذا البرنامج في مواضع أخرى دون الحاجة إلى إعادة تصريفه مرّة أخرى.

لإيقاف العملية بعد خطوة التجميع، استخدم الخيار-c:

g++ -Wall -ansi -c prog.cpp

الربط Linking

الرابط (Linker) هو الذي ينتج مخرجات التصريف النهائية من كائنات الملفّات التي أنتجها المجمّع (Assembler)، وقد تكون المُخرجات إمّا مكتبة مشتركة (أو ديناميكية، رغم تشابه الأسماء، إلا أنّها تختلف عن المكتبات الساكنة المذكورة آنفًا)، أو يمكن أن تكون ملفّا تنفيذيًا.

ويربط جميع الكائنات عن طريق استبدال العناوين الصحيحة بالمراجع التي تشير إلى رموز غير مُعرَّفة (undefined symbols). يمكن تعريف تلك الرموز في ملفّات كائنات أخرى أو في مكتبات أخرى، وإذا عُرِّفت في مكتبات غير المكتبة القياسية، فعليك إخبار الرابط بذلك.

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

التصريف عبر Code::Blocks (واجهة رسومية)

  1. نزِّل Code::Blocks وثبِّته، وإذا كنت تستخدم ويندوز فاحرص على اختيار الملفّ الذي يحتوي اسمُه على ‎mingw‎، إذ لا تثبّت الملفّات الأخرى أي مُصرِّفات.

  2. افتح Code::Blocks وانقر على "Create a new project":

z4Oll.png

  1. اختر "Console application" وانقر على "Go":

0wBAn.png

  1. انقر على"Next"، واختر "C++‎"، انقر على "Next"، اختر اسمًا لمشروعك، واختر مجلدًا لحفظه، ثمّ انقر على "Next" ثمّ على "Finish".

  2. يمكنك الآن تعديل وتصريف شيفرتك، ستجد شيفرة افتراضية تطبع السلسلة النصية "Hello world!" في سطر الأوامر. اضغط على أحد الأزرار الثلاثة compile/run في شريط الأدواتل تصريف و/أو تشغيل البرنامج:

wCmdw.png

للتصريف دون تشغيل، اضغط على gOkY9.png، وللتشغيل دون التصريف مرّة أخرى، اضغط على eLjbt.png، وللتصريف ثمّ التشغيل اضغط على Zgyi8.png.

تصريف وتشغيل البرنامج الافتراضي "مرحبا العالم!" سيُنتِج الخرج التالي:

qbVy8.png

مواصفات الربط Linkage specifications

تخبر مواصفات الربط (linkage specification) المُصرِّفَ بأن يصرِّف التصريحات بطريقة تسمح بربطها بالتصريحات المكتوبة بلغة أخرى، مثل C.

معالج الإشارات الخاصّ بأنظمة التشغيل المشابهة ليونيكس

بما أن معالج الإشارة (signal handler) يُستدعى عن طريق النواة (kernel) باستخدام صيغة الاستدعاء في لغة C، فيجب أن نطلب من االمُصرِّف أن يستخدم تلك الصيغة عند تصريف الدالّة.

volatile sig_atomic_t death_signal = 0;
extern "C" void cleanup(int signum) {
    death_signal = signum;
}
int main() {
    bind(...);
    listen(...);
    signal(SIGTERM, cleanup);
    while (int fd = accept(...)) {
    if (fd == -1 && errno == EINTR && death_signal) {
        printf("Caught signal %d; shutting down\n", death_signal);
        break;
        }
// ...
    }
}

إنشاء مكتبة بلغة C متوافقة مع C++‎

يمكن في العادةً تضمين ترويسات مكتبة C في برامج C++‎، لأنّ معظم التصريحات صالحة في كلا اللغتين. على سبيل المثال، انظر ملف ‎foo.h‎ التالي:

typedef struct Foo {
    int bar;
}
Foo;
Foo make_foo(int);

سيصرَّف تعريف ‎make_foo‎ بشكل منفصل ويُوزّع مع الترويسة على هيئة كائن.

يمكن لبرنامج C++‎ أن يُضمِّن ملف الترويسة ‎foo.h‎ عبر التعبير ‎#include <foo.h>‎، لكن لن يستطيع المُصرِّف أن يعرف أنّ الدالّة ‎make_foo‎ قد عُرِّفت وفق صيغة مكتوبة بلغة C، وسيحاول البحث عنه باسم آخر، وسيفشل في إيجاده.

وحتى لو استطاع إيجاد تعريف ‎make_foo‎ في المكتبة، فليست كل المنصّات تستخدم نفس صيغات الاستدعاء في C و C++‎، وسوف يستخدم مصرّف C++‎ صيغة الاستدعاء الخاصة بـ C++‎ عند استدعاء ‎make_foo‎، وهذا من شأنه أن يتسبّب في حدوث خطأ تجزئة (segmentation fault) في حال كان يُتوقّع أن تُستدعى ‎make_foo‎ وفق صيغة الاستدعاء في C.

يمكن حلّ هذه المشكلة عبر تغليف (wrap) كافة التصريحات في ترويسة في كتلة ‎extern "C"‎، انظر ..

#ifdef __cplusplus
extern "C" {
#endif
typedef struct Foo {
int bar;
} Foo;
Foo make_foo(int);
#ifdef __cplusplus
} /* "extern C" نهاية الكتلة */
#endif

والآن، عند تضمين ‎foo.h‎ من برنامج C، سيظهر كتصريح عادي، أمّا عند تضمين ‎foo.h‎ من برنامج مكتوب بلغة C++‎، فسيكون ‎make_foo‎ داخل كتلة ‎extern "C"‎، وسيعرف المصرّفُ كيف يبحث عن الأسماء، وسيستخدم صيغة الاستدعاء في C.

هذا الدرس جزء من سلسلة دروس عن C++‎.

ترجمة -بتصرّف- للفصل Chapter 138: Compiling and Building والفصل Chapter 134: Linkage specifications من كتاب C++ Notes for Professionals





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن