بسم الله الرحمن الرحيم
عند كتابة الكود[1] توجد بعض العمليات التى تتم عليه فى الخفاء قبل تحويله للنوع الذى ترغب به[2] و من الهام جدا معرفتهم حتى إذا ظهرت لك اخطأ اثناء الترجمه تستطيع تصنيف طبيعة الخطأ و سببه.
فى هذا الموضوع[3]
مصطلحات أساسية (إضافات)[4]
فى الموضوع السابق تناولنا بعض المصطلحات التى تتعلق بالمكونات التى تدخل فى عملية الترجمة، فى هذا الجزء سنتناول المصطلحات التى نستخدمها أثناء عملية الترجمة.
basic source character set
هى الحروف الأساسية[5] التى تمثل برنامج مكتوب بلغة ++C و تتكون من 96 رمز، أيهم لابد من تمثيله فى 7 بت.
extended character set
تحتوى على كافة حروف الأساسية بالإضافة لبعض الحروف التى تتكون من اكثر من بايت (تسمي بـ Escape sequences).
basic execution character set
على الأقل تحتوى على 96 رمز الأساسيين و لكن بترميز ثنائي مختلف و تشترط قواعد اللغة ان قيمة null character تساوى صفر.
extended execution character set
تحتوى على basic execution character set و extended character set و لكن بترميز ثنائي مختلف و تشترط قواعد اللغة ان قيمة null character تساوى صفر.
basic source wide-character set
تماثل basic source character set مع وجوب تمثيل كل عنصر بها داخل النوع wchar_t.
extended wide-character set
تماثل extended character set مع وجوب تمثيل كل عنصر بها داخل النوع wchar_t.
basic execution wide-character set
تماثل basic execution character set مع وجوب تمثيل كل عنصر بها داخل النوع wchar_t.
extended execution wide-character set
تماثل extended execution character set مع وجوب تمثيل كل عنصر بها داخل النوع wchar_t.
implementation-defined behavior
معرف في 1.3.5، هو سلوك يعود لمصصم المترجم تحديدة كما يشاء.
argument, parameter
هو قيمة يتم تمريرها أثناء استدعاء function او macro، بالإضافة لهذا التعريف توجد معلومات أخرى تجعل المفهومين مختلفين و سيتم ذكرهم وقت الحاجة لذلك، راجع 1.3.1 و 1.3.9 لمعرفة الفرق إن أردت.
كلا التعريفين سيتم إستخدام الكلمة معامل للإشارة لأيهم.
well-formed program
معرف في 1.3.14، هو برنامج ++C يوافق قواعد و مفاهيم اللغة و التى تنتج برنامج محدود المعالم.
ill-formed program
معرف في 1.3.4، هو برنامج لا ينطبق عليه مفهوم well-formed program.
implementation limits
معرف فى 1.3.6، هي قيود يتم تطبيقها على برنامج ++C من خلال المترجم.
هذه القيود مثل أقصي عدد من المعاملات داخل function. و أقصي عدد من الحروف التى تكون variable. هذه القيود كل مترجم مطالب بتوثيقها و هى موجوده داخل Annex B(إسم فصل داخل standard).
locale-specific behavior
معرف فى 1.3.7، هو سلوك مبني على خصائص تنتمي لدولة معينة.
multibyte character
معرف فى 1.3.8، هو بايت أو أكثر يمثلوا عنصر موجود داخل extended character set أو extended execution character set أو كلاهما.
universal character name
ذكرت من قبل[6] إمكانية استخدام صيغة معينة لتمثيل حروف Unicode داخل اللغه، هذه الصيغة تأخذ الشكل uXXXX\ و UXXXXXXXX\ حيث الحرف X هو احد ارقام النظام السداسي العشري، سيتم التحدث عن هذه الصيغة بالتفصيل لاحقا.
undefined behavior
معرف فى 1.3.12، هى عملية لا يمكن توقع نتيجتها و الـ Standard تركها للمترجم ليجعلها كما يشاء، مثال:
// Example 1: Null Pointer Dereference int* ptr = 0; *ptr = 10; // undefined behavior // Example 2: Modifying variable more than once between multiple sequence point int a = 0; a = a++ + ++a; // undefined behavior
unspecified behavior
معرف فى 1.3.13، هو سلوك ناتج عن كود سليم و إحتمالات تنفيذه محدده بواسطة الـ Standard و الأمر يعود إلى المترجم لإختيار أحدهم. الكود التالي ينتج unspecified behavior:
// a.cppint a = 10;// b.cppextern int a;int b = a;
س: ما كل هذه الـ character sets، من معرفتي باللغة حروف ASCII هى الوحيدة المستخدمة؟
ج:
أولا: حروف ASCII ليست وحدها المستخدمه داخل اللغه حيث يمكنك إستخدام صيغة universal character name للإشارة لحرف معين داخل Unicode.
ثانيا: ليست كل عناصر اللغة تأتي من ASCII فمثلا إذا اردت وضع علامة سطر جديد داخل نص حينها تستخدم n\ - تسمي Escape Sequence - و حيث انك تراهم حرفين إلا ان اللغه تعتبرهم حرف واحد و الوسيلة التى تستخدمها اللغة لترميز هذه الحروف عن طريق extended character set.
ثالثا: لإعطاء مرونة لمصمي مترجم اللغة تم إضافة ما يسمي بـ execution character set و التى تستخدم داخليا من قبل المترجم لترميز عناصر اللغة، هذه الـ character set انت غير مطالب بمعرفتها إلا إذا قررت تصميم مترجم.
رابعا: حيث ان اللغة تحتوى على نوعين بيانات مختلفين يمكن من خلالهم ترميز النصوص لهذا كان يوجد نسختين من كل character set واحدة لكل نوع بيانات[7].
مراحل الترجمة
معرف في 2.1، توجد تسعة مراحل يقوم بها المترجم لتحويل كود الـ ++C إلى الصيغة الثنائية:
س: لماذا احتاج لمعرفة هذه الخطوات؟
ج: كل مرحلة من مراحل الترجمة لها مجموعة خصائص و اخطأ مرتبطة بها، و معرفتك بهذه المراحل تجعل لديك القدرة على معرفة المرحلة التى نتج منها خطأ معين و بالتالي تستطيع إصلاحه بكل سهولة، أيضا تفتح افاقك لإكمال مهمة معينة بأكثر من إسلوب.
1.1 - يقوم المترجم بتحويل صيغة ملف الكود إلى صيغة خاصة به - execution character set - إذا أراد ذلك.
1.2 - يقوم المترجم بإبدال Trigraph sequences إلى حرف واحد. الـ Trigraph sequences هى مجموعة مكونة من 3 حروف و تبدأ بالحرفين ?? (أنظر الجدول التالي)، الهدف من وجود هذه الصيغة ان بعض code pages لا تحتوى على كافة رموز اللغة لذا بإستخدام الـ Trigraph sequences يتم حل هذه المشكله.
┌──────────────────────────────┬──────────────────────────────┬──────────────────────────────┐
│ trigraph replacement │ trigraph replacement │ trigraph replacement │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ ??= # │ ??( [ │ ??< { │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ ??/ \ │ ??) ] │ ??> } │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ ??' ^ │ ??! | │ ??- ~ │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘مثال، الكود التالي:
??=define arraycheck(a,b) a??(b??) ??!??! b??(a??)
بعد معالجته يصبح الكتالي:
#define arraycheck(a,b) a || b[a]
1.3 - يتم إبدال الحروف التى لا تدخل فى مجموعة الحروف الأساسية بالتسمية التى تقابلها داخل Unicode بإستخدام universal character name أو بصيغة أخرى يختارها المترجم.
2.1 - إذا وجد الرمز \ - يسمى backslash - تبعه مباشرة سطر جديد - الحرف الذى يقابل الرقم 10 فى جدول ASCII - يتم دمج السطر الذى ينتهي بالرمز backslash مع السطر الذى يليه . وجود الرمز backslash قبل السطر الجديد تسمي line splice. مثال، الكود التالي:
#define NUM (2 \ + 3)
يماثل الكود التالي بعد حذف line splice:
#define NUM (2 + 3)
لاحظ كيف تم دمج السطرين بعد ان تم حذف الـ line splice.
الكود التالي لا يحتوى إلا على ملاحظات:
\\ test comment \int main() \{ \ return 0; \}بعد معالجته يصبح كالتالي:
\\ test comment int main() { return 0; }يوجد نوعين من الأسطر يتعامل معها المترجم و هم:
1- physical lines: و هى أسطر الكود الذى كتبها المبرمج، و هذا نفس رقم السطر الذى يظهر فى رسائل الخطأ و التحذير.
2- logical lines: و هى الأسطر الى تنتج بعد الـ line splice.
2.2 - إذا نتج universal character name بعد حذف line splice فهذه الحالة undefined behavior. مثال، الكود التالي:
int \\u0040;
بعد حذف line splice يصبح:
int \u0040;
الصيغة u0040\ تمثل الرمز @ و حيث ان الصيغة وجدت بعد حذف line splice فهذا الكود ينتج undefined behavior.
2.3 - إذا كان ملف الكود غير فارغ و لا ينتهي بسطر جديد فهذا undefined behavior. إذا كان أخر حرفين بملف الكود هم backslash يليها مباشرة سطر جديد فهذا undefined behavior.
3.1 - يتم تقسيم ملف الكود إلى مجموعات تسمي Preproccessing tokens و المساحات البيضاء (التعليقات تعامل كمساحة بيضاء).
3.2 - ملف الكود لابد ألا ينتهي بتعليق غير مكتمل أو جزء من Preproccessing token مثل ان ينتهى ملف الكود بنص غير مكتمل.
3.3 - كل تعليق يتم إبداله بمساحة بيضاء واحدة، المثال التالي:
int/*this is a multiline comment*/a; int b//this is single line comment ;
بعد معالجته يصبح:
int a;int b ;
لاحظ ان التعليق الأول بأكمله تم إبداله بمساحة بيضاء واحده، ايضا التعليق الثاني تم إبداله بمساحة بيضاء واحده.
3.4 - السطر الجديد بعد تعليق السطر الواحد يظل كما هو. من المثال السابق تلاحظ انه لم يتم دمج محتويات السطر الأخير مع الذى قبله لأن السطر قبل الأخير كان يحتوي على تعليق السطر الواحد.
3.5 - المساحات البيضاء المتتالية (سواء كان بينهم سطر جديد او لم يكن) يمكن إبدالهم جميعا بمساحة بيضاء واحدة او تركهم جميعا، الأمر يعود لمصممي المترجم لإختيار ما يريدوا.
3.6 - عملية التعرف على الـ Preproccessing token تعتمد على السياق فمثلا نعلم أن النص يبدأ بـ " و ينتهي بمثلها فإذا حدث و وجد "\ داخل النص لا يفترض ان تعتبر الـ " بها هى نهاية للنص و انما هى جزء منه.
4.1 - الـ Preprocessing directives يتم تنفيذها و أى ماكرو يتم إبداله. و إذا تم إنتاج صيغة تشبة الـ universal character name فهذا undefined behavior.
4.2 - الـ include# تتسبب فى معالجة الملف المذكور للمراحل من 1 و حتى 4.
4.3 - يتم حذف كل الـ Preprocessing directives من الكود.
5 - العناصر universal character name و Escape sequence الموجوده داخل النصوص و الحروف يتم إبدالها بمثيلها داخل execution character set.
6 - النصوص المتتالية المبنية على النوع char يتم دمجها - تسمى بـ ordinary strings - و النصوص المتتالية المبنية على النوع wchar_t يتم دمجها - تسمى بـ wide strings.
7.1 - المساحات البيضاء التى تفصل بين Preproccessing token لم تعد مهمه.
7.2 - يتم تحويل الـ Preproccessing token إلى token. و يتم التحقق من صحتها و من السياق و من ثم يتم تحويلهم لصيغة ثنائية.
8.1 - الـ translation units المحولة يتم التحقق منها للحصول على معلومات كافية بشأن الـ templates المراد الحصول على نسخ فعالة منها إن وجدت.
8.2 - يتم الحصول على نسخ الـ template المراد تحويلها، و الأمر يعود للمترجم للصيغة التى ستكون عليها هذه الـ templates.
8.3 - يتم إجراء التحويلات اللازمه لينتج لنا فى النهاية instantiations uniits و هي الـ translation units بدون templates او الحاجة لأى عمليات تحويل لمحتواها.
8.4 - يعتبر البرنامج ill-formed إذا فشلت أى عملية من عمليات تحويل كود الـ templates.
9 - عمليات الربط الخارجي للدوال يتم معالجتها، أيضا يتم الربط على الكائنات و الدوال من المكتبات الخارجية، و في النهاية يتم إنتاج نسخة تنفيذية و التى تحتوى على المعلومات الكافية لتعمل فى البيئة التى صنع لها.
[1] بدأ من هذا الموضوع يفترض بالقارئ أن يكون على دارية بسيطة بقواعد لغة ++C.
[2] أقصد أن المترجم ينتج ملف تنفيذى(من أى نوع) او مكتبة إستاتيكية و ليس preprocessed file .
[3] هذا الموضوع يحتاج قراءة مسبقة لموضوع مفاهيم أساسية.
[4] سيتم إستخدام هذه المصطلحات مع امثلة توضيحية فى المواضيع القادمة لذا لا تقلق إن لم تستوعبهم جميعهم الأن.
[5] راجع الفقرة الرابعة أبجدية اللغة بموضوع مفاهيم أساسية.
[6] راجع الفقرة الثامنة داخل لغة ++C بموضوع مفاهيم أساسية.
[7]سيتم التحدث عن انواع بيانات اللغة فيما بعد.