-
المساهمات
51 -
تاريخ الانضمام
-
تاريخ آخر زيارة
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو Naser Dakhel
-
سنتطرق في هذا المقال إلى الأنواع الحقيقية والصحيحة في لغة سي C الأنواع الحقيقية سيكون من الأسهل التعامل مع الأنواع الحقيقية real أوّلًا، لأن هناك تفاصيل وتعقيدات أقل بخصوصها موازنةً بنوع الأعداد الطبيعية Integers. يقدّم المعيار تفاصيلًا جديدةً بخصوص دقة ونطاق الأعداد الحقيقية، ويمكن أن تجدهم في ملف الترويسة "float.h" الذي سنناقشه بالتفصيل لاحقًا. هذه التفاصيل مهمة جدًا ولكنها ذات طبيعة تقنية للغاية، ولن تُفهم بالكامل غالبًا إلا من قبل مختصّي التحليل العددي. أنواع الأعداد الحقيقية هي: float: العدد العشري double: العدد العشري مضاعف الدقة long double: العدد العشري الأدق يسمح لنا كل واحد من هذه الأنواع بتمثيل الأعداد الحقيقية بطريقة معينة باستخدام الحاسوب؛ فإذا كان هناك نوعٌ واحدٌ لتمثيل الأعداد الحقيقية، فهذا يعني أن تمثيل الأعداد سيكون متماثلًا بغض النظر عن الاستخدام؛ أما إذا كان العدد يتجاوز الثلاثة أنوع، فهذا يعني أن لغة سي لن تستطيع تصنيف أي من الأنواع الإضافية. يُستخدم النوع float للتمثيل السريع والبسيط للأرقام الصغيرة وهو مشابهٌ للنوع REAL في لغة فورتران؛ أما double فيستخدم للدقة الإضافية، و long double لدقة أكبر من سابقتها. التركيز الأساسي هنا هو أنّ الزيادة في "دقة" كلٍ من float و double و long double تعطي لكل نوعٍ نطاقًا ودقّة مساويةً للنوع الذي يسبقها على الأقل، فأخذ القيمة من متغير نوع double مثلًا، وتخزينها في متغير من نوع long double، يجب أن يمثِّل القيمة ذاتها. لا توجد هناك أي متطلبات للأنواع الثلاثة من متغيرات الأعداد "الحقيقية" لتختلف في خصائصها، وبالتالي إن لم توفّر الآلة سوى نوع واحدٍ من أنواع متغيرات الأعداد الحقيقية، فيمكن عندئذٍ تمثيل جميع أنواع الأعداد الحقيقية الثلاثة في لغة سي بهذا النوع المتوفِّر. لكن بغض النظر، يجب أن يُنظر إلى هذه الأنواع الثلاثة بأنها مختلفة، وكأنّ هناك فرقٌ بينها حقًأ، وهذا يساعد في نقل البرنامج إلى نظام تختلف فيه هذه الأنواع حقًّا، بحيث لن يظهر لك مجموعةٌ من التحذيرات من المصرّف بخصوص عدم توافق الأنواع التي لم تكُن موجودةً على النظام الأول. تسمح لغة سي بمزج جميع أنواع البيانات العددية في التعابير بعكس كثيرٍ من لغات البرمجة الصارمة بخصوص قواعد كتابتها، وذلك يضم مختلف أنواع الأعداد الصحيحة إضافةً إلى الأعداد الحقيقية وأنواع المؤشرات؛ وعندما يتضمن التعبير مزيجًا من أنواع الأعداد الحقيقية والصحيحة، سيُستدعى تحويلٌ ضمني يعمل بدوره على معرفة نوع المزيج الكلّي الناتج. هذه القواعد مهمةٌ جدًا وتدعى التحويلات الحسابية الاعتيادية usual arithmetic conversions، ومن المفيد أن تتذكرها، إذ سنشرح كامل هذه القواعد لاحقًا، إلا أننا سننظر في الوقت الحالي إلى الحالات التي تتضمن مزيجًا من float و double و long double ونحاول فهمها. الحالة الوحيدة التي نحتاج فيها إجراء التحويلات المذكورة هي عندما يُمزج نوعان من البيانات في تعبير، كما في هو موضح في المثال التالي: int f(void){ float f_var; double d_var; long double l_d_var; f_var = 1; d_var = 1; l_d_var = 1; d_var = d_var + f_var; l_d_var = d_var + f_var; return(l_d_var); } [مثال 1.2] نلاحظ في المثال السابق وجود كثيرٍ من التحويلات القسرية، لنبدأ بأسهلها أوّلًا، ولننظر إلى تعيين القيمة الثابتة 1 لكلٍ من المتغيرات الثلاثة. لا بُد من التنويه (كما سيشير القسم الذي يتكلم عن القيم الثابتة constants لاحقًا) إلى أن القيمة 1 هي من نوع int، أي تمثّل عددًا صحيحًا وليس قيمةً ثابتةً حقيقية، ويحوّل الإسناد قيمة العدد الصحيح إلى نوع العدد الحقيقي المناسب والأسهل للتعامل معه. التحويلات المثيرة للاهتمام تأتي بعدها، وأولها ضمن السطر التالي: d_var = d_var + f_var; ما هو نوع التعبير الذي يتضمن العامل +؟ الإجابة عن هذا السؤال سهلة ما دمت ملمًّا ببعض القواعد؛ إذ يُحوَّل النوع الأقل دقةً ضمنيًا إلى النوع الأكثر دقةً وتُنجز العملية الحسابية باستخدام هذه الدقة، وذلك عندما يجتمع نوعان من الأعداد الحقيقية في التعبير ذاته. يتضمن المثال السابق استخدام كلٍّ من double و float، لذلك تُحوّل قيمة المتغير f_var إلى النوع double وتُضاف فيما بعد إلى قيمة النوع double أي المتغير d_var، وتكون نتيجة هذا التعبير هي من نوع double أيضًا، لذا من الواضح أن عملية الإسناد إلى المتغير d_var صائبة. عملية الجمع الثانية أكثر تعقيدّا، ولكنها ما زالت سهلة الفهم، إذ تُحوّل قيمة المتغير f_var وتُجرى العملية الحسابية باستخدام دقة النوع double، ألا وهي عملية جمع المتغيرين، لكن هناك مشكلة، وهي أن نتيجة عملية الجمع من نوع double، لكن عملية الإسناد من نوع long double، ويكون مجدّدًا الحل البديهي في هذه الحالة هو تحويل القيمة الأقل دقة إلى الأكبر دقّة، وهو ما يُجرى ضمنيًّا قبل عملية الإسناد. الآن بعد أن أخذنا نظرةً سريعةً على الأمثلة السهلة، حان وقت الأمثلة الأكثر صعوبة وهي الحالة التي يتسبب فيها التحويل القسري بتحويل نتيجةٍ بدقةٍ عالية إلى دقةٍ أقل منها، ففي مثل هذه الحالات قد يكون من الضروري خسارة الدقة بطريقة محدّدة من تنفيذ التحويل. ببساطة، يجب أن يحدد التنفيذ طريقة تقريب أو اقتطاعٍ للقيمة، وفي أسوأ الحالات قد يكون نوع الهدف غير قادرٍ على تخزين تلك القيمة الضرورية (على سبيل المثال محاولة جمع أكبر قيمة لعدد إلى نفسه)، وتُعد نتيجة التنفيذ في هذه الحالة غير محددة، والبرنامج يشكو من خطأ ولا يمكنك التنبؤ بتصرفه. لا ضرر من تكرار فكرتنا السابقة، إذ يقصد المعيار بحالة السلوك غير المحدد undefined behaviour معنى اسمه حرفيًا، وحالما يدخل البرنامج منطقة السلوك غير المحدد، يمكن لأي شيء أن يحدث؛ فمن الممكن إيقاف البرنامج من طرف نظام التشغيل مصحوبًا برسالة معيّنة؛ أو قد يحدث شيء غير مُلاحظ ويستمر البرنامج للعمل باستخدام قيم خاطئة مُخزَّنة في المتغير. منع البرنامج من إبداء أي سلوك غير محدّد تعد من مسؤولياتك، فتوخّ الحذر. لتلخيص ما سبق: تُجرى العمليات الحسابية التي تتضمن نوعين باستخدام النوع الأعلى دقّة منهما. قد يتضمن الإسناد خسارة لدقة القيمة في حال كان نوع المتغير الهدف ذو دقة أقل من دقة القيمة التي تُسنَد لهذا المتغير. هناك مزيدٌ من التحويلات التي تُجرى عند مزج الأنواع ضمن تعبير واحد، إذ لم نصِف جميعها بعد. طباعة الأعداد الحقيقية يمكن استخدام دالة الخرج التقليدي printf لتنسيق الأعداد الحقيقية وطباعتها، كما يوجد العديد من الطرق لتنسيق هذه الأعداد، ولكننا سنتطرق إلى طريقة واحدة في الوقت الحالي. يوضح الجدول 4.2 التنسيق الموافق لكل نوعٍ من أنواع الأعداد الحقيقية. table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } النوع التنسيق float f% double f% long double Lf% [جدول 4.2. رموز التنسيق للأعداد الحقيقية] ألقِ نظرةً على المثال التالي لتجربة المعلومة السابقة: #include <stdio.h> #include <stdlib.h> #define BOILING 212 /* degrees Fahrenheit */ main(){ float f_var; double d_var; long double l_d_var; int i; i = 0; printf("Fahrenheit to Centigrade\n"); while(i <= BOILING){ l_d_var = 5*(i-32); l_d_var = l_d_var/9; d_var = l_d_var; f_var = l_d_var; printf("%d %f %f %Lf\n", i, f_var, d_var, l_d_var); i = i+1; } exit(EXIT_SUCCESS); } [مثال 2.2] جرّب المثال السابق على حاسوبك الشخصي، ولاحظ النتائج. الأنواع الصحيحة كانت الأنواع الحقيقية أسهل الأنواع، إذ تتصف القوانين الخاصة بالأنواع الصحيحة بتعقيد أكبر، ولكنها ما زالت مفهومة وينبغي تعلُّمها. لحسن الحظ، الأنواع الوحيدة المستخدمة في لغة سي لتخزين البيانات هي الأنواع الحقيقية والصحيحة، إضافةً إلى الهياكل structures والمصفوفات arrays المبنيّة عليهما، إذ لا تحتوي لغة سي على أنواع مميزة للتلاعب بالمحارف، أو التعامل مع القيم البوليانية boolean، وإنما تستخدم الأنواع الصحيحة بدلًا من ذلك، وهذا يعني أنه حالما تفهم الأنواع الصحيحة والحقيقية فأنت تعرف جميع الأنواع. سنبدأ بالنظر إلى الأنواع المختلفة للأعداد الصحيحة وقوانين التحويل فيما بينها. الأعداد الصحيحة البسيطة هناك نوعان من متغيرات الأعداد الصحيحة يطلق عليهما "نكهات flavours"، ويمكن بناء أنواع أخرى انطلاقًا من هذين النوعين كما سنرى لاحقًا، لكن تبقى الأنواع البسيطة int هي الأساس. النوع الأكثر شهرةً هو العدد الصحيح ذو الإشارة signed أو int، أما النوع الأقل شهرة هو العدد الصحيح عديم الإشارة أو unsigned int، ومن المفترض أن تُخزّن القيم في المتغيرات ذات النوع المناسب حسب الآلة التي تشغل البرنامج. عندما تبحث عن نوع بيانات بسيط لتمثيل عدد صحيح، فإن النوع int هو الاختيار البديهي لأي استخدام مُتساهل، مثل عدّاد ضمن حلقة تكرارية قصيرة، إذ لا توجد هناك أي قاعدة تحدّد عدد البتّات التي يخزنها نوع int لقيمةٍ ما، لكنه سيكون دائمًا مساويًا إلى 16 بِت أو أكثر، ويفصِّل ملف الترويسة القياسي "" العدد الفعلي للبتات المُتاحة في تنفيذٍ معين. لم تحتوِ لغة سي القديمة على أية معلومات بخصوص طول متغيّر من نوع int، ولكن الجميع كان يفترض اصطلاحًا أنها على الأقل 16 بِت. في الحقيقة، لا يحدّد ملف الترويسة "" العدد الدقيق للبتات، ولكنه يقدِّم تقديرًا لأعظم عدد وأقل عدد بتات لقيمةٍ في متغيرٍ من نوع int، والقيم التي يحددها هي ما بين 32767 و32767- أي 16 بت فما فوق، سواءٌ كانت عملية المتمم الأحادي أو الثنائي الحسابية مستخدمةُ أم لا، وبالطبع لا يوجد هناك أي قيود من توفير نطاق أكبر في أي الطرفين في حال توفرت الطريقة المناسبة. يتراوح النطاق المحدد وفق المعيار للمتغير من نوع unsigned int من 0 إلى 65535، مما يعني أن القيمة لا يمكن أن تكون سالبة، وسنتكلم بإسهاب عن هذه النقطة لاحقًا. إذا لم تعتَد التفكير بعدد البتات لمتغيّر ما، وبدأت بالقلق عمّا إذا سيؤثر ذلك على قابلية نقل البرنامج كون هذه المشكلة مرتبطةً بوضوح بالآلة (أي الحاسوب الذي يشغّل البرنامج)، فقلقك في محلّه. تأخذ لغة سي قابلية نقل البرنامج على محمل الجدّ كما تدلّك على القيم والمجالات الآمنة، وتشجِّعك أيضًا عوامل العوامل الثنائية bitwise operators على التفكير بعدد البتّات في متغيرٍ ما، لأنها تمنحك الوصول المباشر إلى بتات المتغيرالتي تعالجها بصورةٍ منفردة (كل بت على حدى) أو في مجموعات. ونتيجة لذلك تكوَّن لدى مبرمجي لغة سي المعرفة الكافية بخصوص مشكلات قابلية نقل البرنامج، ممّا يتسبب ببرمجة برامج قابلة للنقل، لكننا لا ننفي هنا إمكانية كتابة برامج غير قابلة للنقل إطلاقًا. متغيرات المحارف المتغير char هو النوع الثاني من أنواع الأعداد الصحيحة البسيطة، فهو نوعٌ آخر من int ولكن بتطبيقٍ مختلف، إذ تُعدّ فكرة تخصيص نوعٍ خاص للتعامل مع المحارف فكرةً جيّدة خاصةً وأن كثيرًا من برامج سي تتعامل بالمحارف، لأن تمثيل القيم باستخدام النوع int يأخذ كثيرًا من المساحة غير الضرورية لتمثيل المحرف. يصف ملف ترويسة الحدود "limits" ثلاثة أشياء عن النوع char ألا وهي: عدد البتات 8 على الأقل. يمكنها تخزين قيمة 127+ على الأقل. القيمة الدنيا للنوع char هي صفر أو أقل، مما يعني أن المجال يتراوح ما بين 0 إلى 127. يحدِّد تنفيذ المتحول char فيما إذا كان سيتصرف تصرُّف المتحولات ذات الإشارة signed أو عديمة الإشارة unsigned. باختصار، تحتل متغيرات المحارف مساحةً أقل من المتغيرات الصحيحة int التقليدية، ويمكن استخدامها لمعالجة المحارف، لكنها تندرج تحت تصنيف الأعداد الصحيحة، ويمكن استخدامها لإجراء العمليات الحسابية كما هو موضح في المثال التالي: include <limits.h> include <stdio.h> include <stdlib.h> main(){ char c; c = CHAR_MIN; while(c != CHAR_MAX){ printf("%d\n", c); c = c+1; } exit(EXIT_SUCCESS); } [مثال 3.2] تشغيل البرنامج في المثال السابق تمرينٌ لك، وربما ستثير النتائج إعجابك. إذا كنت تتسائل عن قيمة CHAR_MIN وCHAR_MAX، فاطّلع على ملف الترويسة limits.h واقرأه. إليك مثالٌ آخر مثيرٌ للإعجاب حقًا، إذ سنستخدم فيه محارف ثابتة constants، والتي يمكن كتابتها بين إشارتين تنصيص أحاديّة على النحو التالي: 'x' لأن القواعد الحسابية تُطبَّق هنا، فسيُحوّل المحرف الثابت السابق ليكون من النوع int، ولكن هذا لا يهم حقًّا لأن قيمة المحرف صغيرة دائمًا ويمكن تخزينها في متغير من نوع char دون فقدان أي دقة (لسوء الحظ هناك بعض الحالات التي لا ينطبق فيها هذا الكلام، تجاهلها في الوقت الحالي). عندما يُطبع محرف باستخدام الرمز c% ضمن دالة printf، يُطبع المحرف كما هو، لكن يمكنك استخدام الرمز d% إذا أردت طباعة قيمة العدد الصحيح الموافقة لهذا المحرف. لماذا استُخدم الرمز d%؟ كما ذكرنا سابقًا، النوع char هو في الحقيقة نوع من أنواع الأعداد الصحيحة. من المهم أيضًا وجود طريقة لقراءة المحارف إلى البرنامج، وتتكفل الدالة getchar بهذه المهمة، إذ تقرأ المحارف من الدخل القياسي standard input للبرنامج وتُعيد قيمةً صحيحةً int موافقة لتخزين هذا المحرف في متغير من نوع char، تخدم هذه القيمة المُمرّرة من نوع int غرضين، هما: تمثيل جميع قيم المحارف الممكنة بواسطتها، إضافةً إلى تمرير قيمة إضافية للدلالة على نهاية الدخل. لا يتسع مجال قيم متغير من نوع char في جميع الحالات لهذه القيمة الإضافية، لذلك يُستخدم النوع int. يقرأ البرنامج التالي الدخل ويعدّ الفواصل والنقاط المُدخلة، وعند وصولة لنهاية الدخل يطبع النتيجة. #include <stdio.h> #include <stdlib.h> main(){ int this_char, comma_count, stop_count; comma_count = stop_count = 0; this_char = getchar(); while(this_char != EOF){ if(this_char == '.') stop_count = stop_count+1; if(this_char == ',') comma_count = comma_count+1; this_char = getchar(); } printf("%d commas, %d stops\n", comma_count, stop_count); exit(EXIT_SUCCESS); } [مثال 4.2] هناك ميزتان نستطيع ملاحظتهما من المثال السابق، الأولى هي الإسناد المتعدّد للعدادين، والثانية هي استخدام الثابت المعرّف EOF؛ وهي قيمة تُمرّر من الدالة getchar في نهاية الدخل وتمثِّل اختصارًا لكلمة نهاية الملف End Of File، وتكون معرفةً ضمن ملف الترويسة ""؛ أما الإسناد المتعدد فهي ميزةٌ شائعةٌ الاستخدام في برامج لغة سي. لنأخذ مثالًا آخر، وليكن برنامجًا لطباعة جميع الأحرف الأبجدية بأحرف صغيرة إذا كان تنفيذك يحتوي على محارف مخزنة بصورةٍ متتالية، أو طباعة نتيجةٍ مثيرةٍ للاهتمام إذا لم يكن كذلك. لا تقدّم لغة سي العديد من الضمانات بترتيب المحارف داخليًّا، لذلك قد يتسبب هذا البرنامج بنتائج مختلفة ويكون غير محمول. #include <stdio.h> #include <stdlib.h> main(){ char c; c = 'a'; while(c <= 'z'){ printf("value %d char %c\n", c, c); c = c+1; } exit(EXIT_SUCCESS); } [مثال 5.2] يذكرنا هذا المثال مرةً أخرى بأن char شكلٌ مختلفٌ من أشكال متغيرات الأعداد الصحيحة ويمكن استخدامه مثل أي عدد صحيح آخر، فهو ليس نوع مميّز بقواعد مختلفة. تصبح المساحة التي يوفرها char موازنةً مع int ملحوظةً ومهمةً عندما يُستخدام الكثير من المتغيرات. تستخدم معظم عمليات معالجة المحارف مصفوفات كبيرة منها وليس محرفًا واحدًا أو اثنين فقط، وفي هذه الحالة يصبح الفرق واضحًا بين الاثنين. لنتخيل سويًّا مصفوفةً مؤلفةً من 1024 متغيرًا من نوع int، تحجز هذه المصفوفة مساحة 4098 بايت (كل بايت 8-بت) من التخزين على معظم الآلات، على افتراض أن طول كل int هو 4 بايت؛ فإذا كانت معمارية الحاسوب تسمح بتخزين هذه المعلومات بطريقة فعّالة، قد تطبّق لغة سي هذا عن طريق متغيرات من نوع char بحيث يأخذ كل متغير بايتًا واحدًا، وبذلك ستأخذ المصفوفة مساحة 1024 بايت، مما سيوفّر مساحة 3072 بايت. لا يهمنا في بعض الحالات إن كان سيوفِّر البرنامج مساحةً أم لا، ولكنه يوفِّرها بغض النظر، ومن الجيد أن تعطينا لغة سي فرصة اختيار نوع المتغير المناسب لاستخدامنا. المزيد من الأنواع المعقدة النوعان السابقان الذين تكلمنا عنهما سابقًا بسيطان، سواءٌ بخصوص تصريحهما أو استخدامهما، ولكن دقتهما في التحكم بالتخزين وسلوكهما غير كافيين في استخدامات نظم البرمجة المعقدة. تقدّم لغة سي أنواعًا إضافية من أنواع الأعداد الصحيحة للتغلُّب على هذه المشكلة وتُقسم إلى تصنيفين، الأنواع ذات الإشارة signed والأنواع عديمة الإشارة unsigned (بالرغم من هذه المصطلحات كلمات محجوز في لغة سي إلا أن معناها يدلّ على غرضها أيضًا)، والفرق بين النوعين واضح؛ إذ يمكن للأنواع ذات الإشارة أن تكون قيمتها سالبة؛ بينما يكون من المستحيل أن تخزِّن الأنواع عديمة الإشارة قيمةً سالبة، وتُستخدم الأنواع عديمة الإشارة في معظم الأحيان لحالتين، هما: إعطاء القيمة دقةً أكبر، أو عندما نضمن أن المتغير لن يخزن أي قيمٍ سالبة في استخدامه، والحالة الثانية هي الحالة الأكثر شيوعًا. تملك الأنواع عديمة الإشارة خاصيةً مميزة ألا وهي أنها ليست عرضةً للطفحان الحسابي overflowing عند إجراء العمليات الحسابية، إذ سيتسبب إضافة 1 إلى متغيرٍ من نوعٍ ذي إشارة يخزّن أكبر قيمة يمكن تخزينها بحدوث طفحان، ويصبح سلوك البرنامج نتيجةً لذلك غير محدّد، ولا يحصل هذا الأمر مع المتغيرات من نوعٍ عديم الإشارة، لأنّها تعمل وفق "باقي قسمة واحد زائد القيمة العظمى التي يمكن للمتغير تخزينها على هذه القيمة"، أي باقي قسمة "max+1)/max)"، والمثال التالي يوضح ما نقصده: #include <stdio.h> #include <stdlib.h> main(){ unsigned int x; x = 0; while(x >= 0){ printf("%u\n", x); x = x+1; } exit(EXIT_SUCCESS); } [مثال 6.2] بفرض أن المتغير x يحتل مساحة 16 بِت، فهذا يعني أن مجال قيمته يترواح بين 0 و 65535، وأن الحلقة التكرارية في المثال ستتكرر لأجل غير مسمّى، إذ أن الشرط التالي محققٌ دائمًا: x >= 0 وذلك بالنسبة لأي متغير عديم الإشارة. يوجد ثلاثة أنواع فرعية لكلٍ من الأعداد الصحيحة ذات الإشارة وعديمة الإشارة، هي: short والنوع الاعتيادي و long، ونستطيع بعد أخذ هذه المعلومة بالحسبان كتابة لائحة بجميع أنواع متغيرات الأعداد الصحيحة في لغة سي باستثناء نوع تخزين المحرف char، على النحو التالي: unsigned short int unsigned int unsigned long int signed short int signed int signed long int ليس مهمًّا استخدام الكلمة المفتاحية signed ويمكن الاستغناء عنها في الأنواع الثلاث الأخيرة، إذ أن نوع int ذو إشارة افتراضيًا، ولكن ينبغي عليك استخدام الكلمة المفتاحية unsigned إذا أردت الحصول على نتيجة مغايرة لذلك. من الممكن أيضًا التخلِّي عن الكلمة المفتاحية int من أي تعليمة تصريح شرط أن تحتوي على كلمة مفتاحية أخرى، مثل long أو short، وسيُفهم المتغير على أنه int ضمنيًّا ولكنه أمرٌ غير محبّذ، على سبيل المثال الكلمة المفتاحية long مساوية للكلمات signed long int. يمنحك النوع long و short تحكّمًا أكبر بمقدار المساحة التي تريد حجزها للمتغير، ولكلّ منهما مجال أدنى محدّد في ملف الترويسة <limits.h>، وهو 16 بِت على الأقل لكل من short و int، و32 بتًا على الأقل للنوع long، سواءً كان ذو إشارة signed أو دون إشارة unsigned. وكما ذكرنا آنفًا من الممكن للتنفيذ أن يحجز مقدارًا يزيد على المقدار الأدنى من البتات إذا أراد ذلك، والقيد الوحيد هنا هو أن حدود المجال يجب أن تكون متساويةً أو محسّنة، وألا تحصل على عددٍ أكبر من البتات في متغيرٍ من نوع أصغر موازنةً بنوعٍ أكبر منه، وهي قاعدة منطقية. أنواع متغيرات المحارف الوحيدة هي signed char و unsigned char، ويتمثّل الفرق بين متغيرات من نوع int و char في أن جميع متغيرات int ذات إشارة إن لم يُذكر عكس ذلك، وهذا لا ينطبق على أنواع المحارف char التي قد تكون ذات إشارة أو عديمة الإشارة اعتمادًا على اختيار المُنفّذ، وعادةً ما يُتخذ القرار بناءً على أسس الكفاءة. يمكنك طبعًا اختيار نوع المتغير قسريًا إذا أردت باستخدام الكلمة المفتاحية الموافقة، ولكن هذه النقطة لا تهمّك إلا في حالة استخدامك لمتغيرات المحارف بنوعها القصير short لتوفير مساحة التخزين. لتلخيص ما سبق: تتضمن أنواع الأعداد الصحيحة short و long و signed و unsigned و النوع الاعتيادي int. النوع الأكثر استخدامًا وشيوعًا هو النوع الاعتيادي int وهو ذو إشارة إلا في حالة تحديد عكس ذلك. يمكن للمتغيرات من نوع char أن تكون ذات إشارة أو عديمة الإشارة حسب تفضيلك، ولكن في حال غياب تخصيصك لها ستُخصّص الحالة الأفضل افتراضيًا. طباعة أنواع الأعداد الصحيحة يمكننا طباعة هذه الأنواع المختلفة أيضًا باستخدام الدالة printf، إذ تعمل متغيرات المحارف بنفس الطريقة التي تعمل بها الأعداد الصحيحة الأخرى، ويمكنك استخدام الترميز القياسي لطباعة محتوياتها (أي العدد الذي يمثل المحرف)، على الرغم من كون القيم الخاصة بها غير مثيرة للاهتمام أو مفيدة في معظم الاستخدامات. نستخدم الرمز c% لطباعة محتويات متغيرات المحارف كما أشرنا سابقًا، كما يمكن طباعة جميع قيم الأعداد الصحيحة بالنظام العشري باستخدام الرمز d% أو ld% لأنواع long، ويوضح الجدول 5.2 المزيد من الرموز المفيدة لطباعة القيم بتنسيقٍ مختلف. لاحظ أنّه في كل حالة تبدأ بالحرف l تُطبع قيمةٌ من نوع long، وهذا التخصيص ليس موجودًا فقط لعرض القيمة الصحيحة بل لتجنُّب السلوك غير المحدد لدالة printf إذا أُدخل الترميز الخاطئ. التنسيق يُستخدم مع c% char (طباعة المحرف) d% القيمة العشرية للأنواع signed int و short و char u% القيمة العشرية للأنواع unsigned int و unsigned short وunsigned char x% القيمة الست عشرية للأنواع int وshort وchar o% القيمة الثمانية للأنواع int وshortوchar ld% القيمة العشرية للنوع signed long lu% lx% lo% كما ذُكر في الأعلى ولكن للنوع long [جدول 5.2. المزيد من رموز التنسيق] سنتكلم على نحوٍ مفصّل حول التنسيق المستخدمة مع الدالة printf لاحقًا. ترجمة -وبتصرف- لقسم من الفصل Variables and Arithmetic من كتاب The C Book. اقرأ أيضًا المقال التالي: التحويلات ما بين الأنواع في تعابير لغة سي C المقال السابق: البنية النصية لبرامج سي C إدارة الذاكرة (Memory management) في لغة C المتغيرات الشرطية وحلها مشاكل التزامن بين العمليات في لغة C متغيرات تقييد الوصول (Semaphores) في لغة البرمجة سي C
-
يشرح هذا المقال تخطيط البرنامج المكتوب بلغة سي C بالإضافة للتعليقات التي يمكن إضافتها عليه والكلمات المفتاحية والمعرفات التي يمكن استخدامها فيه ثم سنعرج على مفهوم التصريح عن المتغيرات في البرنامج. تخطيط البرنامج اعتمدت أمثلتنا حتى اللحظة في تنسيقها على المسافات البادئة indentation والأسطر الجديدة newlines، وهذا الأسلوب في التنسيق شائع في لغات البرمجة التي تنتمي إلى عائلة لغة سي، إذ تُعد هذه اللغات لغات "حرة التنسيق free format" وتُستخدم هذه الحرية في كتابة وتنسيق السطور البرمجية بحيث يحسِّن قراءتها ويبرز تسلسل منطقها. تُستخدم محارف المسافات الفارغة space بما فيها مسافات الجدولة tab الأفقية لإنشاء المسافات البادئة في أي مكان دون أن تؤثّر على عمل البرنامج عدا ضمن المعرِّفات identifiers والكلمات المفتاحية. تعمل الأسطر الجديدة على نحوٍ مماثل لعمل المسافات الفارغة ومسافات الجدولة باستثناء أسطر أوامر المعالج المسبق، الذي يمتلك بنية سطريّة line-by-line. هناك حلان يمكنك اللجوء إليهما في حال كان أحد السطور طويلًا جدًا وغير مريحًا للقراءة، إذ يمكنك استبدال محرف المسافة space بمحرف سطر جديد، بحيث يصبح لديك سطرين بدلًا من سطرٍ واحد. ألقِ نظرةً على المثال التالي للتوضيح: /* a long line */ a = fred + bill * ((this / that) * sqrt(3.14159)); /* the same line */ a = fred + bill * ((this / that) * sqrt(3.14159)); لن تستطيع في بعض الحالات استبدال المحارف بالطريقة السابقة، وذلك بسبب اعتماد المعالج المسبق على "تعليمات" السطر الواحد، ولحلّ هذه المشكلة يمكننا استخدام السلسلة "n\" التي تعني الانتقال لسطرٍ جديد، إذ يصبح هذا المحرف غير مرئي لنظام تصريف لغة سي، ويمكن نتيجةً لذلك استخدام هذه السلسلة في أماكن لا نستطيع استخدام الفراغات فيها في الحالات الاعتيادية، أي ضمن المعرّفات مثلًا أو الكلمات المفتاحية أو السلاسل النصية أو غيرها، وتسبق مرحلة معالجة هذه المحارف مرحلة معالجة ثلاثيات المحارف Trigraphs فقط. /* * Example of the use of line joining */ #define IMPORTANT_BUT_LONG_PREPROCESSOR_TEXT \ printf("this is effectively all ");\ printf("on a single line ");\ printf("because of line-joining\n"); ينبغي أن تُستخدم هذه الطريقة في تقسيم الأسطر (بعيدًا عن أسطر تحكم المعالج المُسبق) في حالة واحدة فقط، ألا وهي لمنع السلاسل النصية الطويلة من أن تختفي إلى اليمين عند النظر لسطور البرنامج. بما أن السطور الجديدة غير مسموحة داخل السلاسل النصية والمحارف الثابتة، قد تعتقد أن هذه الفكرة جيّدة: /* not a good way of folding a string */ printf("This is a very very very\ long string\n"); سيعمل المثال السابق بالتأكيد، ولكن من المحبّذ استخدام ميزة ضمّ السلاسل النصية string-joining التي قُدّمَت ضمن المعيار عند التعامل مع السلاسل النصية: /* This string joining will not work in Old C */ printf("This is a very very very" "long string\n"); يسمح لك المثال الثاني بإضافة الفراغات دون تغيير مضمون السلسلة النصية، إذ إنّ المثال الأول يضيف الفراغات إلى مضمون السلسلة النصية. لكن هل انتبهت أن المثالين يحتويان على خطأ؟ لا يوجد هناك أي مسافة فارغة تسبق الكلمة "long"، مما يعني أن خرج البرنامج سيكون "verylong" دون مسافة بين الكلمتين. التعليق تكلمنا عن التعليق سابقًا وقلنا أن التعليق يبدأ بالمحرفَين "*/" وينتهي بالمحرفين "/*"، ويُترجم التعليق إلى مسافة فارغة واحدة أينما وجد وهو يتبِّع القوانين ذاتها الخاصة بالمسافة الفارغة. من المهم هنا معرفة أن هذا التعليق لا يختفي -كما كان الحال في لغة سي القديمة- ومن غير الممكن وضع التعليق بداخل سلسلة نصية أو محرف ثابت وإلا أصبح جزءًا منهما: /*"This is comment"*/ "/*The quotes mean that this is a string*/" لم تكن لغة C القديمة واضحةً بشأن تفاصيل حذف التعليق، إذ كان من الممكن أن يكون الناتج هنا: int/**/egral(); هو حذف التعليق، أي أن المصرف سينظر للتعليمة السابقة بكونها استدعاءً لدالة اسمها integral، لكنه سيُبدَّل التعليق بالاعتماد على معيار سي بمسافة فارغة وستكون التعليمة السابقة مساويةً للتعليمة التالية: int egral(); التي تصرح عن دالة باسم egral تُعيد قيمةً من نوع int. مراحل الترجمة تُجرى ترجمة المحارف المختلفة وضمّ الأسطر والتعرف على التعليقات ومراحل أخرى من الترجمة المبكّرة وفق ترتيبٍ معيّن، إذ يقول المعيار أن هذه المراحل تحدث بالترتيب التالي: ترجمة المحارف الثلاثية. ضمّ الأسطر. ترجمة التعليقات إلى مسافات فارغة (عدا التعليقات الموجودة ضمن السلاسل النصية والمحارف الثابتة)، إذ من الممكن في هذه المرحلة جمع عدّة مسافات فارغة إلى مسافة فارغة وحيدة. ترجمة البرنامج. تُنهى كل مرحلة قبل أن تبدأ المرحلة التي تليها. الكلمات المفتاحية والمعرفات بعد أن تكلمنا عن أبجدية لغة سي، سنلقي نظرةً على مزيدٍ من عناصر اللغة المثيرة للاهتمام، إذ تُعد الكلمات المفتاحية keywords والمعرفات identifiers المكونات الأكثر وضوحًا، وعلى الرغم من تشابه تركيبها إلا أنها مختلفة. الكلمات المفتاحية تحجز لغة سي مجموعةً صغيرةً من الكلمات المفتاحية لاستخدامها الخاص، ولا يمكن استعمال هذه الكلمات المفتاحية على أنها معرّفات داخل البرنامج، وهذا أمرٌ شائعٌ في معظم لغات البرمجة الحديثة. قد يتفاجئ بعض مستخدمو لغة سي القديمة بوجود بعض الكلمات المفتاحية الجديدة، وإن كانت هذه الكلمات المفتاحية مُستخدمةٌ مثل معرّفات في برامج سابقة فيجب عليك تغييرها، ولحسن الحظ الانتباه لهذا النوع من الأخطاء والعثور عليها سهل وبسيط، إذ سيخبرك المصرّف أن هناك بعض الأسماء غير الصالحة. يضمّ الجدول التالي الكلمات المفتاحية المُستخدمة في معيار سي، إذ ستلاحظ أن جميع الكلمات لا تبدأ بأحرف كبيرة. table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } struct int double auto switch long else break typedef register enum case union return extern char unsigned short float const void signed for continue volatile sizeof goto default while static if do [جدول 3.2. كلمات مفتاحية] الكلمات المفتاحية المُضافة جديدًا التي قد تفاجئ المبرمجين السابقين للغة سي هي: const وsigned وvoid وvolatile (على الرغم من وجود void منذ فترة). سيلاحظ بعض القراء شديدي الانتباه أن الكلمات المفتاحية entry و asm و fortran غير موجودة في الجدول، إذ أنها ليست من ضمن المعيار، وسيفتقدها القليل منهم فقط. المعرفات يُعد المعرف Identifier مرادفًا لما نطلق عليه "اسم name"، وتُستخدم المعرفات في لغة سي للدلالة على العديد من الأشياء، فقد لاحظنا استخدامها حتى الآن في تسمية المتغيرات والدوال، ولكنها تُستخدم أيضًا في تسمية مزيدٍ من الأشياء التي لم نراها بعد، مثل: العناوين labels "وسوم" الهياكل tags of structures الاتحادات unions المعدّدات enums قواعد إنشاء معرف بسيطةً جدًا، إذ يمكنك استخدام الأحرف البالغ عددها 52 حرفًا من الأبجدية الإنجليزية (أحرف كبيرة أو صغيرة)، والأرقام العشرة من 0 حتى 9 وأخيرًا الشرطة السفلية "_"، التي يمكن عدّها حرفًا من الأبجدية في حالتنا هذه، لكن هناك قيدٌ واحدٌ ألا وهو أن المعرّف يجب أن يبدأ بحرف أبجدي. على الرغم من أن المعيار لا ينص صراحةً على حد أقصى لطول اسم المعرّف، إلا أننا نحتاج أن نتكلم عن هذه النقطة، إذ لا يوجد هناك أي حد في لغة سي القديمة ومعيار سي لطول اسم المعرف، ولكن المشكلة هي أنه لا يوجد هناك أي ضمانات أن جميع محارف اسم المعرف تُفحص أثناء موازنة المساواة، فقد كان حد الموازنة في لغة سي القديمة 8 محارف، أما في المعيار فهو 31 محرف. وبذلك يكون عمليًّا الحد الجديد للمعرف في المعيار هو 31 محرف، ومع ذلك يمكن للمعرفات تجاوز هذا الطول ولكنها يجب أن تختلف في أول 31 محرفًا إذا كنت تريد التأكُّد من أن برامجك محمولة portable. يسمح المعيار بالأسماء الطويلة لبعض التطبيقات، لذا إذا استخدمت الأسماء الطويلة وكان لا بدّ منها، فتأكد من أنها فريدةً قبل أن يتوقف التحقُّق من اسمها في المحرف ذو الرقم 31. يُعد طول المعرفات الخارجية external identifiers واحدًا من أكثر الأشياء المثيرة للجدل في الإصدار الجديد؛ إذ تُعرف المعرفات الخارجية بأنها المعرفات التي يجب أن تكون مرئية خارج نطاق الشيفرة المصدرية المستخدمة فيها، وتُعد برامج المكتبة أو الدوال التي يجب أن تُستدعى من عدّة ملفات مصدرية مثالًا جيدًا عليها. اختار المعيار الحفاظ على القيود القديمة التي تخص هذه المعرفات، إذ لا تعدّ المعرفات الخارجية مختلفةً عن بعضها إلا في حالة اختلافها مع بعضها في المحارف الستّ الأولى، وليزداد الأمر سوءًا فقد تُعامل الأحرف الكبيرة والصغيرة بنفس الطريقة؛ والسبب وراء هذا عملي، إذ أن معظم أنظمة تصريف لغة سي تعمل بمساعدة أدوات نظام معينة للربط بين دوال المكتبات والبرنامج المكتوب بلغة سي C، وهذه الأدوات هي خارج تحكم مصرّف لغة C، ولذا على المعيار أن يضع بعض الحدود العملية التي ستتوافق مع شروط هذه الأدوات. لا يوجد أي قيود إجبارية على عدد الأحرف، ولكن الالتزام بهذا القيد (الأحرف الست الأولى متكافئة الحالة بين الأحرف الكبيرة والصغيرة) يزيد من فرصة عمل البرنامج على مختلف الأجهزة دون مشاكل (برنامج محمول). يذكرنا المعيار دائمًا بأنه ينظر إلى استخدام تكافؤ الحالة بين الأحرف الصغيرة والكبيرة إضافةً لحدّ المحارف في تسمية المعرفات على كونها ميزاتٍ قديمة، ومن الممكن أن يلغي المعيار القادم استخدام هذه القيود. لنأمل أن يحصل ذلك قريبًا. التصريح عن المتغيرات ذكرنا في المقالات السابقة أنه يجب التصريح عن أسماء الأشياء قبل استخدامها، والاستثناء الوحيد هنا لهذه القاعدة هو أسماء الدوال التي تُعيد قيمةً من النوع int، لأنه مصرّحٌ عنها افتراضيًا بالإضافة لأسماء العناوين labels. بإمكانك إما التصريح declaration عن الأشياء، وهي العملية التي تصف اسم ونوع الشيء ولكنها لا تحجز أيّ مكان على الذاكرة، أو التعريف definition، الذي يحجز مكانًا في الذاكرة للشيء المُصرَّح عنه. الفرق بين التصريح والتعريف مهم، وللأسف فإنّ الكلمتين متشابهتان مما يسبب الخلط لدى الكثير، ومن هذه النقطة فصاعدًا سنستخدم هاتين الكلمتين في سياقهما الصحيح، لذلك إذا نسيت الفرق بين المصطلحين وأردت التأكد مرةً أخرى فارجع لهذه الفقرة. القواعد المتعلقة بجعل التصريح ضمن التعريف معقّدةٌ بعض الشيء، لذا سنؤجلها ونكتفي حاليًّا ببعض الأمثلة والقواعد التي ستؤدي الغرض لأمثلتنا القادمة. /* * A function is only defined if its body is given * so this is a declaration but not a definition */ int func_dec(void); /* * Because this function has a body, it is also * a definition. * Any variables declared inside will be definitions, * unless the keyword 'extern' is used. * Don't use 'extern' until you understand it! */ int def_func(void){ float f_var; /* a definition */ int counter; /* another definition */ int rand_num(void); /* declare (but not define) another function */ return(0); } سنستمرّ قدُمًا في القسم التالي ونتكلم عن نوع المتغيرات والتعابير. ترجمة -وبتصرف- لقسم من الفصل Variables and Arithmetic من كتاب The C Book. اقرأ أيضًا المقال التالي: الأنواع الحقيقية والصحيحة في لغة سي C المقال السابق: المحارف المستخدمة في لغة سي C بنية برنامج لغة سي C
-
سنلقي في الجزئية الثانية من السلسلة، نظرةً على الأجزاء التي لم نلقِ لها بالًا في المقال السابق من هذه السلسلة، الذي كان بمثابة مقدمة سريعة عن لغة سي، التحدي هنا هو التكلم عن أساسيات اللغة بصورةٍ موسّعة وكافية للسماح لك بفهم المزيد عن اللغة دون إغراق المبتدئين بالمعلومات والتفاصيل غير الضرورية في هذه المرحلة. سنغطي في هذه الجزئية من السلسلة بعض المفاهيم والمشاكل الدقيقة التي لا تقرأ عنها في النصوص التقديمية للغة، فيجب عليك أن تقرأه بعقلية منفتحة وبمزاج جيّد. قد يجد دماغك المرهَق التمارين الموجودة بين الفقرات استراحةً مفيدة، إذ ننصحك بشدّة أن تحاول حلّ التمارين هذه بينما تقرأ المقال، إذ أن ذلك من شأنه أن يساعدك في موازنة الكفة بين تعلُّم المفاهيم الجديدة -التي قد تشعر في بعض المراحل بغزارتها- والتمارين. حان الوقت لتقديم بعض الأساسيات في لغة سي. المحارف المستخدمة في لغة سي هذه الفقرة مثيرة للاهتمام، وسندعو المحارف المُستخدمة في لغة سي بأبجدية سي C، وهذه الأبجدية مهمة جدًا، وربما يكون هذا الجزء الوحيد من هذا المقال الذي يمكنك قراءته بصورةٍ سطحية وفهم جميع محتوياته من المرة الأولى. لذلك، اقرأه لتضمن أنك تعرف محتوياته والمعلومات الواردة فيه جيّدًا وتذكر أن تعود إليه في حال أردت مرجعًا بهذا الخصوص. الأبجدية الاعتيادية تعرّف قلةٌ من لغات البرمجة أبجديتها أو تلقي بالًا لهذا الأمر، إذ أن هناك افتراضًا مسبقًا بأن أحرف الأبجدية الإنجليزية وخليطًا من علامات الترقيم والرموز ستكون متاحةً في أي بيئة داعمة للّغة، ولكن هذا الافتراض غير محقّقٍ دائمًا. تعاني لغات البرمجة القديمة من هذه المشكلة بدرجةٍ أقل حدّة، ولكن تخيل إرسال برنامج مكتوب بلغة سي عبر جهاز تلكس Telex أو عن طريق نظام بريد إلكتروني يحتوي على بعض القيود، أتعي أهمية الأمر الآن؟ يوصّف المعيار مجموعتين مختلفتين من المحارف: واحدةٌ تُكتب بها البرامج وأخرى تُنفَّذ بها، وذلك للسماح للأنظمة المختلفة بتصريف البرنامج وتنفيذه بغض النظر عن اختلاف طرق ترميز المحارف لكل نظام. في الحقيقة، الأمر مهمّ فقط في حال استخدامك محارفًا ثابتة constant في المعالج المُسبق preprocessor، إذ من الممكن أن تختلف قيم هذه المحارف عند التنفيذ، وهذا السلوك معرّف عند التنفيذ implementation-defined، فهو موثّق بالتأكيد، ولكن لا تقلق بخصوص هذا الأمر الآن. يملي المعيار وجود أبجدية مؤلفة من 96 رمزًا للغة سي، وهي: المسافات ومسافات الجدولة الأفقية والعمودية ومحرف السطر الجديد وفاصل الصفحة table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 ! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ {|} ~ [جدول 1 أبجدية لغة سي] اتّضح أن معظم أبجديات الحاسوب الشائعة تحتوي على جميع الرموز اللازمة للغة سي، عدا بعض الحالات الشاذة النادرة مثل المحارف الموجودة في الأسفل، والتي تُعد مثالًا عن محارف أبجدية لغة سي المفقودة من مجموعة المحارف ذات 7 بت لمعيار منظمة المعايير العالمية International Standards Organization المدعوّ ISO 646، وهي مجموعة جزئية من المحارف المُستخدمة في أبجديات الحاسوب على نطاقٍ واسع. # [ \ ] ^ { | } ~ لتضمين هذه الأنظمة التي لا تحتوي على مجموعة المحارف البالغ عددها 96 والمطلوبة لكتابة برامج بلغة سي، حدّد المعيار طريقةً لاستخدام معيار ISO 646 لتمثيل المحارف المفقودة ألا وهي تقنية ثُلاثيات المحارف trigraphs. ثلاثيات المحارف ثلاثيات المحارف Trigraphs هي سلسلةٌ من ثلاثة محارف ضمن المعيار ISO 646، وتُعامل معاملة محرفٍ واحدٍ ضمن أبجدية لغة سي. تبدأ جميع ثلاثيات المحارف بعلامتَي استفهام "??"، ويساعد هذا في الدلالة على أن هناك شيءٌ "خارجٌ عن المألوف" ضمن البرنامج. يوضّح الجدول 2 أدناه جميع ثلاثيات المحارف المُعرّفة ضمن المعيار. محرف أبجدية سي C ثلاثي المحرف # =?? ] )?? [ (?? } >?? { <?? \ /?? \ !?? ~ -?? ^ '?? [جدول 2 ثلاثيات المحارف] دعنا نفترض مثلًا، أن طرفيتك terminal لا تحتوي على الرمز "#" لكتابة سطر المعالج المُسبق التالي: #define MAX 32767 عندها، نستطيع استخدام طريقة ثلاثيات المحارف على النحو التالي: ??=define MAX 32767 ستعمل ثلاثيات المحارف حتى لو كان المحرف "#" موجودًا، ولكن هذه التقنية موجودةٌ لمساعدتك في الحالات الحرجة ولا يُحبَّذ استخدامها بديلًا عن محارف أبجدية سي دومًا. ترتبط إشارة الاستفهام "?" بالمحرف الواقع على يمينها، لذا في أي سلسلة مؤلفة من عدّة إشارات استفهام، تشكِّل إشارتان فقط ضمن السلسلة ثلاثي محارف، ويعتمد المحرف الذي تمثِّله على المحرف الذي يقع بعد الإشارتين مباشرةً، وهذا من شأنه أن يجنّبك كثيرًا من الالتباس. من الخطأ الاعتقاد أن البرامج المكتوبة بلغة سي، والتي تلقي بالًا كبيرًا لإمكانية تشغيلها على نحوٍ محمول portable وعلى جميع الأنظمة مكتوبةٌ باستخدام ثلاثيات المحارف "إلا في حال ضرورة نقلها إلى نظام يدعم معيار ISO 646 فقط"؛ فإذا كان نظامك يدعم جميع المحارف البالغ عددها 96 محرفًأ، واللازمة لكتابة البرامج بلغة سي، فيجب استخدامها دون الاستعانة بثلاثيات المحارف، إذ وُجدت هذه التقنية لتنفيذها ضمن بيئات محدودة، ومن السهل جدًا كتابة مفسّر يحوِّل برنامجك بين التمثيلين عن طريق فحص كل محرف بمحرفه. ستميّز جميع المصرّفات المتوافقة مع المعيار ثلاثيات المحارف عندما تجدها، إذ أن تبديل ثلاثيات الأحرف هو من أولى العمليات التي يجريها المصرّف على البرنامج. المحارف متعددة البايت أُضيف دعم المحارف متعددة البايت multibyte characters إلى المعيار، ولكن ما هو السبب؟ تتضمن نسبةٌ كبيرة من الحوسبة الاعتيادية اليومية بياناتٍ ممثّلة بنص بشكلٍ أو بآخر، وساد الاعتقاد في مجال الحوسبة أنه من الكافي دعم ما يقارب مئة محرف مطبوع فقط، ومن هنا كان عدد المحارف الممثلة لأبجدية سي 96 محرفًا، وذلك بناءً على متطلبات اللغة الإنجليزية وهذا الأمر ليس مفاجئًا بالنظر إلى أن معظم سوق تطوير البرمجيات والحوسبة التجاري كان في الولايات المتحدة الأمريكية. تُعرف مجموعة المحارف هذه باسم المخزون repertoire وتتناسب مع 7 أو 8 بتات من الذاكرة، وهذا السبب في أهمية استخدام 8-بت واحدةً لتخزين وقياس للبيانات بصورةٍ أساسية في معيار US-ASCII ومعمارية الحواسيب المصغرة minicomputers والحواسيب الدقيقة microcomputers. تتّبع لغة سي أيضًا توجّهًا في تخزين البيانات باستخدام البايت byte، إذ أن أصغر وحدات التخزين الممكن استخدامها مباشرةً في لغة سي هي البايت، والمعرّفة بكونها تتألف من 8 بِت. قد تشكو الأنظمة والمعماريات القديمة التي لم تُصمم مباشرةً لدعم ذلك من بطء بسيط في الأداء عند تشغيل لغة سي، لكن لا ينظر معظم الناس لذلك الأمر بكونه مشكلةً كبيرة. لربما كانت الأبجدية الإنجليزية مقبولةً لتطبيقات معالجة البيانات حول العالم في وقتٍ مضى، إذ استُخدمت الحواسيب في أماكن يتوقّع من مستخدميها الاعتياد على هذا الأمر، لكن هذا لا يصلح في زمننا الحالي؛ إذ أصبح من الضروري في وقتنا المعاصر تزويد الأنظمة بوسائل معالجة البيانات وتخزينها باللغة الأم لمستخدميها. قد نستطيع حشر جميع المحارف المستخدمة في لغات كلٍّ من الولايات المتحدة الأمريكية وأوروبا الغربية ضمن مجموعة محارف لا يتجاوز حجمها 8 بِت لكل محرف، ولكن الأمر مستحيلٌ بالنسبة للغات الآسيوية. هناك طريقتان لتوسعة مجموعة المحارف، الأولى عن طريق إضافة عددٍ معين من البايتات (عادةً اثنين) لكل محرف، وهذه هي الطريقة المصممة لدعم محارف أكبر حجمًا في لغة سي؛ أما الطريقة الأخرى فهي باستخدام مخطط ترميز الإدخال بالإزاحة shift-in والخرج بالإزاحة shift-out؛ وهو ترميزٌ شائع في قنوات الاتصال ذات سعة 8-بت. ألقِ نظرةً على سلسلة المحارف التالية: a b c <SI> a b g <SO> x y إذ يعني المحرف <SI> "بدّل إلى اليونانية" والمحرف <SO> "بدّل مجددًَا إلى الإنجليزية"، ويعرض جهاز العرض المُهيّأ للعمل وفق هذا الترميز النتيجة على النحو التالي: a, b, c, alpha, beta, gamma, x, y. هذه هي الطريقة المُتبعة تقريبًا في معيار shift-JIS الياباني، والاختلاف في ذلك المعيار هو أن المحارف الموجودة ضمن الإدخال بالإزاحة تتألف من مِحرفين يُستخدمان في تمثيل محرفٍ واحدٍ باللغة اليابانية، وهناك العديد من الطرق البديلة التي تستخدم عدة محارف مدخلة بالإزاحة، ولكنها أقل شيوعًا. يسمح المعيار الآن باستخدام مجموعات المحارف الموسّعة extended character sets، إذ تُستخدم المحارف المُعرفة مُسبقًا والبالغ عددها 96 في كتابة برنامج بلغة سي، ولكن مجموعة المحارف المُوسعة مسموحة في كتابة التعليقات والسلاسل النصية والمحارف الثابتة وأسماء ملفات الترويسة (جميع ما ذُكر يمثل بيانات وليسَ جزءًا من كتابة البرنامج). يضع المعيار بعض القواعد البديهية لوصف طريقة استخدام هذه المجموعات، ولن نكررها هنا، لكن أبرزها هو أن البايت ذو القيمة الصفرية يُفسّر محرفًا فارغًا null بغض النظر عن أي حالة إزاحة. هذا المحرف مهم لأن لغة سي ترمز لنهاية السلسلة النصية به وتعتمد عليه العديد من دوال المكتبات. هناك متطلبٌ آخر ألا وهو أن سلاسل المحارف متعددة البايت يجب أن تبدأ وتنتهي ضمن حالة الإزاحة المبدئية. يصف المعيار النوع char بكونه مناسبًا لتخزين قيمة جميع المحارف ضمن "مجموعة محارف التنفيذ execution character set" التي ستكون مُعرفةً في توثيق نظامك؛ وهذا يعني (في المثال السابق) أن نوع char يمكنه تخزين 'a' أو 'b' أو محرف الإزاحة إلى اللغة اليونانية بنفسه <SI>، إذ لا يوجد أي فرق في القيم المخزنة بداخل المتغير من نوع char بسبب تقنية الإدخال والإخراج بالإزاحة؛ وهذا يعني أنه لا يمكننا تمثيل 'a' على أنه محرف "ألفا" في اللغة اليونانية، وحتى نستطيع تحقيق ذلك يلزمنا أكثر من 8 بتات، وهو أكبر من حجم char في معظم الأنظمة، وهنا يأتي دور نوع المتغير wchar_t الذي قدَّمه المعيار، ولكن يجب تضمين ملف الترويسة <stddef.h> قبل استخدامه، لأن wchar_t معرّفٌ على أنه اسم بديل عن نوعٍ موجودٍ في لغة سي. سنناقش هذا الأمر بتوسع أكبر لاحقًا. ختامًا، نستطيع تلخيص ما سبق: تتطلب لغة سي مجموعة محارف عددها 96 محرف على الأقل لاستخدامها في محارف الشيفرة المصدرية للبرنامج. لا تحتوي جميع مجموعات المحارف على 96 محرف، بالتالي تسمح ثلاثيات المحارف للمعيار ISO 646 الأساسي كتابة برامج سي في حال الضرورة. أُضيفت المحارف متعددة البايت حديثًا مع المعيار، وتدعم: المحارف متعددة البايت المُرمّزة بالإزاحة Shift-encoded multibyte characters، التي تسمح بحشر المحارف الإضافية ضمن سلاسل محارف "اعتيادية"، بحيث يمكننا استخدام النوع char معها. المحارف الموسّعة wide characters التي تتسع لمساحة أكبر من المحارف الاعتيادية، ولها نوع بيانات مختلف عن النوع char. ترجمة -وبتصرف- لقسم من الفصل Variables and Arithmetic من كتاب The C Book. اقرأ أيضًا المقال التالي: البنية النصية لبرامج سي C المقال السابق: بعض البرامج البسيطة بلغة سي C: المصفوفات والعمليات الحسابية بنية برنامج لغة سي C
-
بما أننا ما زلنا في مرحلة التكلم عن لغة سي عمومًا، دعونا نتناول مثالين آخرين، إذ ستجد أن بعض التعليمات الموجودة ضمن هذين المثالين أصبحت معروفةً بالنسبة لك ولن نتطرّق إليها، ولكننا سنتكلّم عن المزايا والتعليمات الجديدة التي لم نشرحها بعد عن اللغة. برنامج لإيجاد الأعداد الأولية /* * * * Dumb program that generates prime numbers. */ #include <stdio.h> #include <stdlib.h> main(){ int this_number, divisor, not_prime; this_number = 3; while(this_number < 10000){ divisor = this_number / 2; not_prime = 0; while(divisor > 1){ if(this_number % divisor == 0){ not_prime = 1; divisor = 0; } else divisor = divisor-1; } if(not_prime == 0) printf("%d is a prime number\n", this_number); this_number = this_number + 1; } exit(EXIT_SUCCESS); } [مثال 2.1] هناك الكثير من الأمور الجديدة المثيرة للاهتمام في هذا البرنامج. أوّلًا، البرنامج يعمل بغباء بعض الشيء، إذ يقسِّم العدد على جميع الأرقام الواقعة في المجال بين نصف قيمته والرقم 2 للتحقّق ما إذا كان أوليًّا أم لا، وإذا كان هناك أي قسمةٍ دون باقٍ فهذا يعني أنه ليس أوليًّا. هذه المرة الأولى التي نستخدم فيها كلًا من العامل % وعامل المساواة المُمثَّل بإشارتَي مساواة ==، إذ يتسبب هذا العامل بكثير من الأخطاء في برنامجك عادةً، نظرًا للخلط بينه وبين عامل الإسناد =، فكن حذرًا. المشكلة في فحص المساواة هذا وأيّ فحص آخر، هي أن استبدال == بالرمز = هو تعليمة صالحة، ففي الحالة الأولى (استخدام ==) تُوازَن القيمتان وتُفحص حالة المساواة، كما في المثال التالي: if(a == b) while (c == d) كما يمكن استخدام عامل الإسناد = في الموضع ذاته، ولكن لإسناد القيمة الواقعة على يسار الإشارة للمتغيّر على يمينها. تصبح المشكلة مهمةً أكثر إذا كنت معتادًا على البرمجة باللغات التي تستخدم عامل المساواة بصورةٍ مماثلة لما تستخدمه لغة سي لعامل الإسناد، إذ لا يوجد أي شيء يمكنك فعله لتغيير ذلك سوى الاعتياد على هذا الأمر من الآن فصاعدًا، ولحسن الحظ تعطيك بعض المصرّفات الحديثة بعض التحذيرات عندما تلاحظ استخدامًا غريبًا لعامل الإسناد في غير موضعه المعتاد، ولكن يصبح الأمر مزعجًا أحيانًا إذا كان هذا فعلًا ما تريد فعله وليس خطأً. نلاحظ في مثالنا السابق أيضًا أول استخدام للتعليمة if، إذ تفحص هذه التعليمة تعبيرًا expression موجودًا داخل القوسين على نحوٍ مشابه لتعليمة while، إذ تتطلب جميع التعليمات الشرطية التي تتحكّم بتدفق البرنامج تعبيرًا محتوًى داخل قوسين بعد الكلمة المفتاحية، وفي هذه الحالة الكلمة المفتاحية هي if. تُكتب التعليمة if على النحو التالي: if(expression) statement if(expression) statement else statement يوضِّح المثال المبين أعلاه أن التعليمة تأتي بنموذجين، إذ يحتوي الأول على الكلمة if والثاني على الكلمة else؛ فإذا كان التعبير صحيحًا وفقًا للنموذج الأول، فستُنفَّذ مجموعة التعليمات الموجودة داخل متن if، أما إذا كان التعبير خاطئًا فلن تُنفّذ؛ بينما تنفّذ التعليمات الموجودة ضمن else وفقًا للنموذج الثاني فقط في حالة كان التعبير خاطئًا (أي تعبير if السابق لها). يتميز استخدام تعليمة if بمشكلة شائعة، ألقِ نظرةً على المثال التالي وأجِب، هل ستُنفَّذ التعليمة statement-2 أم لا؟ if(1 > 0) if(1 < 0) statement-1 else statement-2 الإجابة هي نعم ستُنفَّذ التعليمة. لا تركّز تفكيرك على مسافة الإزاحة فهي مضللةٌ غالبًا، فقد تكون تعليمة else تابعةً لتعليمة if الأولى أو الثانية بحسب وصف كلٍّ منهما، لذا علينا اللجوء لقاعدةٍ ما لجعل الأمور أوضح. القاعدة ببساطة هي أن else تابعةٌ لتعليمة if الأقرب (التي تسبق else)، والتي لا تحتوي على تعليمة else مسبقًا. لنجعل البرنامج يعمل كما نريد وفق تنسيق مسافات الإزاحة، علينا إنشاء تعليمة مركبة باستخدام الأقواس المعقوصة كما يلي: if(1 > 0){ if(1 < 0) statement-1 } else statement-2 تتبَع لغة سي -على الأقل في هذه الحالة- لعمل معظم لغات البرمجة واصطلاحاتهم. في الحقيقة، قد يشعر بعض القراء أن هذه القاعدة "بديهية" إن سبق وتعاملوا مع لغة برمجة مشابهة للغة سي وأنها لا تستحق الذكر. لنأمل أن يفكر الجميع بهذه الطريقة. عامل القسمة يُشار إلى عامل القسمة بالرمز "/"، وعامل باقي القسمة بالرمز "%". تفعل عملية القسمة المتوقّع منها، إلا أن إجراء القسمة على أعداد صحيحة int ستعطي نتيجةً مقربةً باتجاه الحد الأدنى (الصفر)، إذ تعطي العملية 5/2 مثلًا النتيجة 2، وتعطي التعليمة 5/3 النتيجة 1؛ وللحصول على القيمة المقتطعة من الناتج نستخدم عامل باقي القسمة، إذ تعطينا العملية 2%5 النتيجة 1، والعملية 3%5 النتيجة 2. تعتمد إشارة باقي القسمة وناتج القسمة على المقسوم والمقسوم عليه وهي مُعرّفة في المعيار. مثال عن تنفيذ عملية الدخل من المفيد أن نكون قادرين على الحصول على الدخل وكتابة برامج قادرة على طباعة وعرض النتائج ضمن قوائم أو جداول في الخرج، والطريقة الأبسط لتنفيذ هذا هي عن طريق استخدام الدالة getchar، وهي الطريقة الوحيدة التي سنناقشها حاليًّا؛ إذ تقرأ هذه الدالة كلّ محرف على حدى من دخل البرنامج وتعيد قيمةً عدديّة int تمثّله، ويمكن استخدام هذه القيمة الممثلة للمحرف في طباعة المحرف ذاته في خرج البرنامج، ويمكن أيضًا موازنة هذا المحرف (القيمة) مع محارف معيّنة أو محارف قُرأت مسبقًا، إلا أن الاستخدام الأكثر منطقية هو موازنة المحرف المُدخل مع محرف معيّن. لا تُعد موازنة قيمة المحارف ما إذا كانت أصغر أو أكبر خيارًا جيّدًا، إذ لا يوجد أي ضمان بأن قيمة a أصغر من قيمة b، مع أن ذلك الأمر محققٌ في معظم الأنظمة، ولكن الضمان الوحيد هنا الذي يقدمه لك المعيار هو أن القيمة ستكون متتابعة من 0 إلى 9. ألقِ نظرةً على المثال التالي: #include <stdio.h> #include <stdlib.h> main(){ int ch; ch = getchar(); while(ch != 'a'){ if(ch != '\n') printf("ch was %c, value %d\n", ch, ch); ch = getchar(); } exit(EXIT_SUCCESS); } [مثال 3.1] هناك ملاحظتان جديرتان بالاهتمام، هما: سنجد في نهاية كل سطر دخل المحرف n\ (محرف ثابت)، وهو المحرف ذاته الذي نستخدمه في دالة printf عندما نريد طباعة سطر جديد. نظام الدخل والخرج الخاص بلغة سي غير مبني على مفهوم الأسطر بل على مفهوم المحارف؛ فإذا كنت تريد التفكير بالأمر من منطلق مفهوم الأسطر، فانظر للمحرف n\ على أنه إعلان لنهاية السطر. استخدام c% لطباعة المحرف بواسطة الدالة printf، إذ يسمح لنا هذا الأمر بطباعة المحرف على أنه محرف على الشاشة، بالموازنة مع استخدام d% الذي سيطبع المحرف ذاته ولكن بتمثيله العددي المُستخدم ضمن البرنامج. إذا جربت تنفيذ هذا البرنامج بنفسك، قد تجد أن بعض الأنظمة لا تمرّر المحرف الواحد تلو الآخر عند كتابته، بل تجبرك على كتابة سطر كاملٍ للدخل أولًا، ثم تبدأ معالجة المحرف الواحد تلو الآخر. قد يبدو الأمر مشوّشًا لبعض المبتدئين عندما يكتبون محرفًا ما ولا يحدث شيء بعد ذلك، وليس للغة سي أي علاقةٍ بذلك، بل يعتمد الأمر على حاسوبك ونظام تشغيله. المصفوفات البسيطة يكون غالبًا استخدام المصفوفات arrays في لغة سي للمبتدئين بمثابة تحدٍ، إذ أن التصريح عن المصفوفات ليس صعبًا، بالأخص المصفوفات أحادية البعد one-dimensional، ولكن سبب الأخطاء هنا هو بدء الدليل index الخاص بها من الرقم 0. للتصريح عن مصفوفة مؤلفة من 5 أعداد من نوع int، نكتب: int something[5]; تستخدم لغة سي الأقواس المعقوفة square brackets للتصريح عن المصفوفات، ولا تدعم أيّ مصفوفة لا تقع أدلتها بين 0 وما فوق. العناصر الصالحة في مثالنا هي [something[0 إلى [something[4، و [something[5 غير موجود في المصفوفة وهو عنصر غير صالح. يقرأ البرنامج التالي المحارف من الدخل، ويرتبها وفقًا لقيمتها العددية، ويطبعها في الخرج مرةً أخرى. ألقِ نظرةً على البرنامج وحاول فهم ما يحصل، إذ لن نتكلم عن الخوارزمية مفصلًا في شرحنا. #include <stdio.h> #include <stdlib.h> #define ARSIZE 10 main(){ int ch_arr[ARSIZE],count1; int count2, stop, lastchar; lastchar = 0; stop = 0; /* * Read characters into array. * Stop if end of line, or array full. */ while(stop != 1){ ch_arr[lastchar] = getchar(); if(ch_arr[lastchar] == '\n') stop = 1; else lastchar = lastchar + 1; if(lastchar == ARSIZE) stop = 1; } lastchar = lastchar-1; /* * Now the traditional bubble sort. */ count1 = 0; while(count1 < lastchar){ count2 = count1 + 1; while(count2 <= lastchar){ if(ch_arr[count1] > ch_arr[count2]){ /* swap */ int temp; temp = ch_arr[count1]; ch_arr[count1] = ch_arr[count2]; ch_arr[count2] = temp; } count2 = count2 + 1; } count1 = count1 + 1; } count1 = 0; while(count1 <= lastchar){ printf("%c\n", ch_arr[count1]); count1 = count1 + 1; } exit(EXIT_SUCCESS); } [مثال 4.1] ستلاحظ استخدام الثابت المُعرّف ARSIZE في كل مكان ضمن المثال السابق بدلًا من كتابة حجم المصفوفة الفعلي بصورةٍ صريحة، ويمكننا بفضل ذلك تغيير العدد الأقصى من المحارف الممكن ترتيبها ضمن هذا البرنامج بتغيير سطرٍ واحدٍ منه وإعادة تصريفه. الانتباه إلى امتلاء المصفوفة هي نقطة غير مشدّد عليها ولكنها هامّة لأمان برنامجنا؛ فإذا نظرت بتمعّن للمثال، ستجد أن البرنامج يتوقف عند تمرير العنصر ذو الدليل ARSIZE-1 للمصفوفة، وذلك لأن أي مصفوفة بحجم N عنصر تحتوي العناصر من 0 إلى N-1 فقط أي ما مجموعه N عنصر. على عكس بعض اللغات، لا تُعْلمك لغة سي أنك وصلت إلى نهاية المصفوفة، بل تُنتج ذلك بما يعرف باسم التصرف غير المحدد undefined behaviour في البرنامج، وهذا ما يتسبب ببعض الأخطاء الغامضة في برنامجك. يتجنّب المبرمجون الخبراء هذا الخطأ عن طريق اختبار البرنامج المتكرّر للتأكد من عدم حصول ذلك عند تطبيق الخوارزميّة المستخدمة، أو عن طريق فحص القيمة قبل محاولة الحصول عليها من المصفوفة. وتُعد هذه المشكلة من أبرز مصادر أخطاء وقت التشغيل run-time في لغة سي، لقد حذرتك! خلاصة القول: تبدأ المصفوفات بالدليل 0 دائمًا، ولا يمكنك تجنّب هذا الاصطلاح. تحتوي المصفوفة من الحجم "N" على عناصر من الدليل "0" إلى الدليل "N-1"، والعنصر "N" غير موجود داخل المصفوفة هذه، ومحاولة الوصول إليه هو خطأ فادح. مصطلحات هناك نوعان من الأشياء المختلفة في البرامج المكتوبة بلغة سي، أشياء تُستخدم لتخزين القيَم، وأشياء تدعى بالدوال، وبدلًا عن الإشارة للشيئين بصورةٍ منفصلة بعبارة طويلة، نعتقد أنه من الأفضل أن نرمز إليهما بتسميةٍ واحدةٍ عامّة ألا وهي "الكائنات objects"، وسنستخدم هذه التسمية كثيرًا في الفصول القادمة، إذ يتبع الشيئان نفس القواعد إلى حدٍّ ما؛ ولكن يجدر الذكر هنا إلى أن معنى المصطلح هذا مختلف عمّا يقصده المعيار، إذ يُستخدم مصطلح "كائن" في المعيار على نحوٍ خاص لوصف منطقة من الذاكرة المحجوزة لتمثيل قيمة، والدالة مختلفة عن هذا التعريف تمامًا. يؤدي هذا لاستخدام المعيار المصطلحين على نحوٍ منفصل وغالبًا ما ستجد العبارة "… الدوال والكائنات …"؛ ونظرًا لأن هذا الاختلاف لا يؤدي لكثيرٍ من الخلط ويحسِّن قراءة النص في كثيرٍ من الحالات، فسنستمرّ في استخدام المصطلح العامّ "كائن" للدلالة على الدوال والقيَم، وسنستخدم المصطلح "دالة" و"كائن بيانات Data object" عندما نريد التمييز بين الاثنين وفقًا للحالة. لذا، قد تجد اختلافًا بسيطًا في المعنى إن كنت تقرأ المعيار. ترجمة -وبتصرف- لقسم من الفصل Chapter 1 An Introduction to C من كتاب The C Book. اقرأ أيضًا المقال السابق: بنية برنامج لغة سي C إدارة الذاكرة (Memory management) في لغة C المتغيرات الشرطية وحلها مشاكل التزامن بين العمليات في لغة C متغيرات تقييد الوصول (Semaphores) في لغة البرمجة سي C
-
إذا كنت معتادًا على لغات تتبع بنية الكُتل مثل لغة باسكال Pascal، فستجد بنية برنامج لغة سي C مفاجئًا لك؛ وإذا كانت خبرتك في مجال لغات مشابهة للغة فورتران FORTRAN، فستجد البنية مشابهةً لما اعتدت عليه -بالرغم من اختلافها بصورةٍ كبيرة في التفاصيل، وفي الحقيقة استعارت لغة سي من كلا الأسلوبين المُستخدمين بصورةٍ واضحة، ومن أماكن أخرى أيضًا. نتيجةً لأخذ بعض القواعد من مصادر مختلفة، تبدو لغة سي أشبه بنتيجة تزاوج فصيلة كلاب ترير Terrier غير الأنيقة والمعروفة بعنادها وقوتها لكنها متسامحة مع أفراد العائلة. يطلق علماء الأحياء على هذا النوع من الفصائل "القوة الهجينة"، قد يذكرك كلامنا أيضًا بمخلوق كمير Chimera الأسطوري الذي يبدو خليطًا من الخرفان والماعز، قد يمنحنا الحليب والصوف، ولكنه سيزعجنا بثغائه المرتفع ورائحته غير اللطيفة. إذا نظرنا للأمر عمومًا نلاحظ أن ميزة لغة سي C العامة هي بنية البرنامج الموزعة على عدة ملفات، لأنها تسمح بتصريف منفصل لهذه الملفات، إذ تسمح لغة سي بتوزيع أجزاء من برنامج مكتمل على عدة ملفات مصدرية والتصريف على نحوٍ متفرق عن بعضها بعضًا. مبدأ العمل هنا هو أن جميع عمليات التصريف هذه ستعطينا ملفات يمكن ربطها Linked سويًّا عن طريق أي محرر ربط، أو محمّل ربط يستخدمه نظامك، ولكن بنية الكتل لبعض لغات البرمجة المشابهة للغة ألغول ALGOL تجعل هذه الطريقة صعبة التنفيذ، نظرًا لأن البرنامج مكتوبٌ بطريقة تجعل منه كتلةً واحدةً مترابطة، إلا أن هناك بعض الطرق للتغلُّب على هذه المشكلة. تتغلب لغة سي على هذه المشكلة بطريقةٍ مثيرة للاهتمام، إذ من المفترض أن تسرّع عملية التصريف، لأن تصريف البرنامج إلى تعليمات مصرَّفة Object Code بطيء ومكلف من ناحية الموارد، فالتصريف عملية شاقة. أتت من هنا فكرة استخدام المحمّل في ربط مجموعة من التعليمات المصرفة، إذ تتطلب هذه العملية ترتيب التعليمات حسب عنوانها للتوصل للبرنامج الكامل ببساطة. يُفترض أن يكون هذا الحل خفيف الاستهلاك للموارد، وعلى المحمّل طبعًا فحص المكتبات المستخدمة في التعليمات المصرفة واختيارها أيضًا. الفائدة المكتسبة هنا هو أننا لسنا بحاجة تصريف كامل البرنامج بعد تعديل جزءٍ بسيط منه، في هذه الحالة نحن بحاجة إعادة تصريف الجزء المُعدّل من البرنامج فقط؛ ولكن قد يصبح المحمّل في بعض الأحيان أبطأ عمليات تصريف البرنامج وأكثرها استهلاكًا للموارد بزيادة العمل المطلوب منه، وقد تكون العملية أسرع في بعض الأنظمة إذا صُرّف كل شيء دفعةً واحدة، وتُعد لغة أدا Ada إحدى الأمثلة المعروفة باتباعها لهذا الأسلوب. بالنسبة للغة سي فالعمل المنجز من المصرف ليس ضخمًا ومعقول إلى حدٍّ ما، يوضح الشكل 1.1 طريقة عمل المصرف في لغة سي. هذه الطريقة مهمة في لغة سي، إذ من الشائع أن تجد جميع البرامج باستثناء الصغيرة منها مؤلفةٌ من عددٍ من ملفات الشيفرة المصدرية، هذا يعني أيضًا أن جميع البرامج مهما كانت بسيطة ستمرّ بالمحمّل، نظرًا لاعتماد لغة سي المكثف على المكتبات، وهذا ما قد يكون غير واضح عند النظرة الأولى أو للمتعلّم الجديد. الدوال تتكون لغة C من مجموعة عناصر تشكل لبنات البناء الأساسية لها، مثل الدوالّ Functions وما نطلق عليه تسمية المتغيرات العامة global variables، إذ تُسمى هذه العناصر في نقطة ما من البرنامج عند تعريفها، وتحتوي طريقة الوصول لهذه العناصر باستخدام اسمائهم ضمن البرنامج على بعض القواعد، وتُوصف هذه القواعد في المعيار بمصطلح الربط Linkage. سنتكلم في الوقت الحالي فقط عن الربط الخارجي External Linkage وانعدام الربط No linkage، إذ تُدعى العناصر التي يمكن الوصول إليها ضمن البرنامج كاملًا، مثل دوال مكتبة معينة، بعناصر الربط الخارجي، وتُستخدم العناصر عديمة الربط بكثرة أيضًا ولكن الوصول إليها محدودٌ بصورةٍ أكبر. تُسمى المتغيرات المستخدمة داخل الدالة بالمتغيرات "المحلية Local" وهي عديمة الربط، وعلى الرغم من أننا نتفادى المصطلحات المعقدة قدر الإمكان في هذا الكتاب مثل المصطلحات التي ذكرناها سابقًا، ولكن لا توجد طريقة أبسط من شرح هذه المصطلحات. ستألف مصطلح الربط ضمن هذا الكتاب، والنوع الوحيد من الربط الخارجي الذي ستراه حاليًّا هو استخدام الدوال. تكافئ الدوال في لغة سي الدوال والبرامج الفرعية في لغة فورتران FORTRAN والدوال والإجراءات في لغة باسكال Pascal وألغول ALGOL، بينما لا تمتلك لغة بيسك BASIC ومعظم طفراتها mutations البسيطة أو لغة كوبول COBOL مقدار الدوال التي تمتلكه لغة سي. الفكرة من الدالة بسيطة وتتمثّل بإعطائك الإمكانية بتضمين فكرةٍ واحدة أو عملية ضمن قالب، وإعطائها اسمًا ما واستدعائها ضمن أجزاءٍ مختلفة من برنامجك باستخدام اسمها فقط. تكون تفاصيل العملية هذه غير واضحة عند ذكر الاسم ضمن البرنامج، وهذا الأمر طبيعي. وفي البرامج المصممة المهيكلة بصورةٍ جيدة، يمكنك تغيير طريقة عمل دالة ما (شرط ألّا يغير ذلك من طبيعة العمل) دون أن تؤثر على الأجزاء الأخرى من البرنامج. هناك دالةٌ ذات اسم مميز في البيئات المستضافة ألا وهي دالة "main"، إذ تمثِّل هذه الدالة نقطة بداية البرنامج عند تشغيله، أما في البيئات المستقلة فالذي يحدد أولى خطوات البرنامج هي دالة معرفة مسبقًا Implementation defined؛ وهذا يعني أنه على الرغم من أن المعيار لا يحدد ما الذي سيحدث، إلا أن سلوك البرنامج يجب أن يكون محدّدًا وموثقًا. يتوقف البرنامج عندما يغادر دالة "main". ألقِ نظرةً على البرنامج البسيط التالي الذي يحتوي على دالتَين: #include <stdio.h> /* * Tell the compiler that we intend * to use a function called show_message. * It has no arguments and returns no value * This is the "declaration". * */ void show_message(void); /* * Another function, but this includes the body of * the function. This is a "definition". */ main(){ int count; count = 0; while(count < 10){ show_message(); count = count + 1; } return(0); } /* * The body of the simple function. * This is now a "definition". */ void show_message(void){ printf("hello\n"); } [تمرين 1.1] شرح تمرين 1.1 ما الذي احتواه التمرين السابق؟ يمكن لمثال صغير جدًا أن يقدِّم لك الكثير عن لغة سي، إذ احتوى التمرين السابق على دالتين وتعليمة include#، وبعض التعليقات، بالإضافة لأشياء أخرى، وبما أنّ التعليق هو أبسط الأجزاء في البرنامج دعونا نبدأ به. التنسيق والتعليق تخطيط برنامج مكتوب بلغة سي ليس مهمًّا للمصرف، ولكنه هام لنقل بعض المعلومات عن البرنامج للقارئ البشري ولتسهيل عملية قراءته، إذ تسمح لك لغة سي باستخدام محرف المسافة space ومسافات الجدولة tabs والسطور الجديدة newline دون أن تؤثر على عمل البرنامج. تُدعى المحارف الثلاث المذكور سابقًا باسم المسافة الفارغة white space، ولا يميّز بينها المصرّف لأنها ببساطة تغير من موضع الكلمات دون التأثير "مرئيًّا" على ما يُعرض على جهاز الخرج. يمكن أن نلاحظ المسافة البيضاء في أي مكان من البرنامج عدا وسط المعرّفات Identifiers والسلاسل النصية Strings والمحارف الثابتة Character constants؛ إذ نقصد بالمعرفات هنا اسم الدالة أو كائن Object آخر، لا تشغل بالك بالسلاسل النصية والمحارف الثابتة إذ سنناقشها في الفصول القادمة. يُعد الفصل بين شيئين مختلفين من الحالات التي يصبح فيها استخدام المسافات الفارغة ضروريًّا، إذ قد يتسبب تلاصقهما بتفسيرهما شيئًا آخر، مثل الجزء void show_message، إذ نحتاج لمسافة فارغة للفصل بين الكلمتين void وshow_message، لكن احتواء )show_message على مسافة بيضاء بينها وبين القوس المفتوح غير ضروري، ووجودها مجرّد أسلوب تنسيقي في كتابة الشيفرة لا أكثر. يبدأ التعليق في لغة C باستخدام المحرفين "*/" المتلاصقين دون أي فراغ بينهما، ويُعد أي شيء يأتي بعدهما إضافةً للمحرفين "/*" مسافةً فارغةً واحدة. لم يكن الأمر مماثلًا في معيار سي القديم، إذ كانت تنص القاعدة على إمكانية استخدام التعليق في أي مكان يمكن أن تُستخدم فيه المسافة الفارغة، أما الآن يُعد التعليق بحد ذاته مسافةً فارغة؛ التغيير الحاصل طفيف وستكون الأمور أكثر وضوحًا في جزئية لاحقة من هذه السلسلة عندما نناقش المعالج المُسبق preprocessor. يجعل تنسيق تعليقات لغة سي بهذا الشكل تضمين تعليق بداخل تعليق آخر غير ممكنٍ، نظرًا لإغلاق أول زوج محارف "*/" في التعليق الثاني كتلة التعليق، وهذا إزعاجٌ بسيط ستعتاد عليه لاحقًا. من الممارسات الشائعة وضع زوج المحارف "*/" في بداية كل سطر من تعليق متعدد الأسطر لإبراز كلّ منهم، كما هو موضحٌ في مثالنا السابق. تعليمات المعالج المسبق التعليمة الأولى في مثالنا السابق هي موجّه للمعالج المُسبق Preprocessor directive، إذ تضمَّن مصرّف لغة سي سابقًا مرحلتين، هما المعالجة المُسبقة ومرحلة التصريف الاعتيادية؛ إذ تمثّل المعالجة المُسبقة معالج ماكرو macro processor تجري بعض عمليات التلاعب النصي البسيطة على البرنامج قبل أن تمرّر النص الناتج إلى المصرّف، وقد أصبحت عملية المعالجة المسبقة جزءًا أساسيًّا من عمل المصرّف ولهذا تُعد جزءًا لا يتجزّأ من اللغة. على الرغم من أن لغة سي حساسة لما يقع في نهاية الأسطر إلا أن عملية المعالجة المسبقة تلاحظ الأسطر فقط، ومع ذلك من الممكن كتابة موجّه معالجة مسبقة متعدد الأسطر، ولكنه غير شائع، وستشعر بالغرابة عندما تجد هذه الطريقة متبعة. يُعد أي سطرٍ يبدأ بالمحرف # توجيهًا للمعالج المسبق. في التمرين 1.1 يؤدي موجّه المعالجة المسبقة include# إلى تبديل السطر هذا بمحتويات ملف آخر، وفي هذه الحالة اسم الملف يوجد ما بين القوسين < و>، وهذه طريقة شائعة لتضمين محتوى ملفات الترويسات Header files القياسية ضمن برنامجك، دون الاضطرار لكتابتها بنفسك. يحتوي ملف <stdio.h> المهم العديد من المعلومات الضرورية التي تسمح لك باستخدام مكتبة الدخل والخرج القياسية، بهدف الحصول على الدخل وإظهار الخرج، فإذا أردت استخدام هذه المكتبة عليك أن تتأكد من وجود <stdio.h>. كان معيار سي السابق متساهلًا أكثر بهذا الشأن. تعليمات التعريف Define تُعد تعليمة define# من الإمكانيات الأخرى والمستخدمة كثيرًا للمُعالج المُسبق، إذ تُستخدم على النحو التالي: #define IDENTIFIER replacement تعني التعليمة السابقة أنه على المعالج المُسبق استبدال جميع الكلمات المطابقة إلى IDENTIFIER بالكلمة replacement عند جميع نقاط ورودها ضمن البرنامج. يُكتب دائمًا المعرِّف بأحرف كبيرة IDENTIFIER، وهذا اصطلاحٌ برمجي يساعد قارئ الشيفرة على فهمها، أما الجزء المُبدّل به replacement فقد يكون أي سلسلة نصية. تذكر أن المعالج المسبق لا يعرف قواعد لغة سي، إذ أن مهمته هي التلاعب النصي فقط. يكون الاستخدام الأكثر شيوعًا لهذه التعليمة هو التصريح Declare عن أسماء القيم العددية الثابتة كما هو موضح فيما يلي: #define PI 3.141592 #define SECS_PER_MIN 60 #define MINS_PER_HOUR 60 #define HOURS_PER_DAY 24 واستخدام القيم على النحو التالي: circumf = 2*PI*radius; if(timer >= SECS_PER_MIN){ mins = mins+1; timer = timer - SECS_PER_MIN; } سيكون الخرج الناتج عن المعالج المُسبق مماثلًا فيما لو كتبت الشيفرة التالية: circumf = 2*3.141592*radius; if(timer >= 60){ mins = mins+1; timer = timer - 60; } أخيرًا، نستطيع تلخيص ما سبق على النحو التالي: تتعامل تعليمات المعالج المُسبق مع الملفات النصية سطرًا بسطر، على نقيض لغة سي. تُستخدم التعليمات من النوع include# لقراءة محتوًى من ملفٍ معين، عادةً لتسهيل التعامل مع دوال مكتبةٍ ما. تُستخدم تعليمات define# لتسمية الثوابت، وتُكتب الأسماء اصطلاحًا بحروفٍ كبيرة. تعريف وتصريح الدالة التصريح نلاحظ وجود ما يُسمى تصريح الدالة function declaration بعد تضمين ملف <stdio.h>، الذي يخبر المصرّف أن show_message دالة لا تأخذ أي وسيط ولا تُعيد أي قيمة، ويوضح لنا هذا تغييرًا جرى على المعيار، ألا وهو النموذج الأولي للدالة function prototype، وسنناقش هذا الموضوع بتوسّع لاحقًا. ليس من الضروري التصريح عن الدالة مسبقًا، إذ ستستخدم لغة سي بعض القواعد القديمة الافتراضية في هذه الحالة، إلا أنه ينصح بشدة التصريح عن الدالة في البداية. الفرق بين التصريح والتعريف هو أن التصريح يصف نوع الدالة والوسطاء المُمرّرة له، بينما يحتوي التعريف على بنية الدالة بالكامل. سيهمّنا فهم الفرق بين المصطلحين لاحقًا. يستطيع المصرّف تفقد استعمال الدالة show_message فيما إذا كان صحيحًا أم لا بالتصريح المُسبق عنها قبل استخدامها، ويصف التصريح ثلاث خصائص عن الدالة، هي: اسمها ونوعها وعدد الوسطاء ونوعهم؛ إذ يشير الجزء )void show_message إلى نوع الدالة والقيمة التي تُعيدها بعد استدعائها وهي void (سنناقش معناها لاحقًا). نستطيع رؤية الاستخدام الثاني لكلمة void في قائمة الوسطاء للدالة (void)، والذي يعني أن الدالة لا تقبل أي وسطاء. التعريف تلاحظ في نهاية البرنامج تعريف الدالة، وبالرغم من أنّ أن طولها ثلاث أسطر فقط، إلا أنها تُعد مثالًا على تعريف دالة متكامل. تنفِّذ دوال لغة سي المهام التي قد تقسِّمها بعض اللغات الأخرى إلى جزأين، إذ تستخدم لغات البرمجة الدوال لإعادة قيمةٍ ما، مثل دالة الجيب المثلثي sin وجيب التمام cos أو ربما دالة تُعيد الجذر التربيعي لعددٍ ما، وهذه الطريقة التي تعمل بها دوالّ لغة سي، بينما تجري بعض لغات البرمجة هذه العملية باستخدام ما يشبه الدوال ولكن الفرق هنا هو عدم إعادة القيمة، مثل استخدام فورتران للبرامج الفرعية واستخدام باسكال وألغول للإجراءات. تنجز لغة سي كل هذه المهام باستخدام الدوال عن طريق تحديد نوع القيمة المُعادة عند التصريح عن الدالة، ولا تُعيد الدالة show_message أي قيمة، لذلك نوعها void. إما أن يكون استخدام القيمة void بديهيًّا لك، أو صعب الفهم حسب الطريقة التي تنظر لها للأمر. ففي الحقيقة، يمكننا الدخول في نقاشات فلسفيّة جانبية وغير مثمرة عن كون void يصف نوع قيمةٍ أو لا، لكن أفضل تجنب ذلك. بغض النظر عن رأيك، استخدام النوع void التي تعني: "أنا لا أهتم بأي قيمة ترجعها هذه الدالة (أو لا ترجعها)". إذًا، نوع الدالة هو void واسمها show_message، أما القوسان () اللذان يتبعان هذه المعلومات، فهما لتنبيه المصرّف أننا نقصد التعريف، أو التصريح عن دالة. إذا كانت الدالة تقبل أي وسيط، فيجب وضع اسمه داخل القوسين. الدالة التي نتكلم عليها في مثالنا لا تأخذ أي وسطاء وهذا الأمر موضّح عن طريق استعمال الكلمة void بداخل القوسين. وبالتالي اتّضح أن للكلمة التي تصف الفراغ والرفض أهميّةٌ بالغة. يشكّل متن الدالة تعليمة مركبة compound statement، وهي مجموعةٌ من التعليمات المُحاطة بأقواس معقوصة {}، على الرغم من وجود تعليمة واحدة فقط إلا أن الأقواس مطلوبة، وعمومًا، تسمح لك لغة سي باستخدام تعليمة مركبة في أي مكان تسمح به عادةً باستخدام تعليمة واحدة بسيطة، وتهدف الأقواس المعقوصة لتحويل عدة تعليمات متتالية إلى تعليمة واحدة. إذا سألت السؤال المبرَّر "هل يتوجب استخدام الأقواس المعقوصة في كل مكان، إذا كان الهدف منها جمع عدّة تعليمات لتعليمة واحدة؟" الإجابة: نعم، عليك استخدام الأقواس المعقوصة، والمكان الوحيد الذي لا تستطيع فيه استخدام تعليمة واحدة عوضًا عن تعليمة مركبة هو عند تعريف دالةٍ ما. بالتالي، أبسط دالة يمكننا إنشاؤها هي دالةٌ فارغة، لا تفعل أي عملٍ إطلاقًا: void do_nothing(void){} التعليمة الموجودة بداخل دالة show_message هي استدعاءٌ لدالةٍ من مكتبة printf، إذ تُستخدم هذه الدالة لتنسيق وطباعة الأشياء، والاستخدام الموجود في المثال هو أبسط استخدامات هذه الدالة. تقبل الدالة printf وسيطًا واحدًا أو أكثر، التي تُمرَّر قيمهم من استدعاء الدالة إلى الدالة نفسها، في مثالنا هذا، الوسيط هو السلسلة النصية. يُفسّر محتوى السلسلة النصية من الدالة printf وتتحكم الدالة بطريقة عرض الخرج حسب الوسطاء الممرّرة له، يماثل عمل الدالة عمل تعليمة FORMAT في فورتران إلى حدٍّ ما. خُلاصة القول: تُستخدم التصريحات في التصريح عن اسم الدالة ونوع القيمة المُعادة ونوع قيم الوسطاء إن وُجدت. تعريف الدالة هي تصريح للدالة مع محتواها أيضًا. يُصرَّح عن الدالة التي لا تعيد أي قيمة باستخدام الكلمة void، على سبيل المثال: ;void func(/* list of arguments */) يجب أن يصرّح عن الدالة التي لا تأخذ أي وسيط باستخدام الكلمة void ضمن لائحة الوسطاء، على سبيل المثال: ;void func(void) السلاسل النصية تُعرَّف السلاسل النصية في لغة سي بأنها سلسلة من المحارف المحتواة داخل علامتي تنصيص على النحو التالي: "like this" من غير المسموح أن تشغر السلسلة النصية عدة أسطر، إذ تُعد عنصرًا واحدًا بصورةٍ مشابهة للمعرفات. إلا أنه من الممكن استخدام محرفَي المسافة ومسافة الجدولة داخل السلسلة النصية. يوضح المثال أدناه سلسلةً نصيّةً صالحةً وأخرى غير صالحة في السطرين الثاني والثالث: "This is a valid string" "This has a newline in it and is NOT a valid string" يمكنك الحصول على سلسلة نصية طويلة جدًا بالاستفادة من حقيقة أن المحرف "\" في نهاية السطر يختفي تمامًا ضمن برنامج سي عند التنفيذ: "This would not be valid but doesn't have \ a newline in it as far as the compiler is concerned" الحل الثاني هو باستخدام ميّزة ضم السلاسل النصية، التي تنظر لكل سلسلتين نصيتين متجاورتين على أنهما سلسلةٌ نصيةٌ واحدة: "All this " "comes out as " "just one string" بالعودة للمثال 1.1، تُعد السلسلة النصية "n\" مثالًا على ما يُدعى محرف الهروب escape character ويمثّل في حالتنا هذه حالة إنشاء سطر جديد، وستطبع الدالة printf محتوى السلسلة النصية على ملف برنامج الخرج، بحيث سيكون الخرج "Hello" متبوعًا بسطرٍ جديد. يسمح المعيار الجديد باستخدام المحارف ذات البايتات المتعدّدة multibyte characters لدعم الأشخاص العاملين ببيئة تستخدم مجموعة محارف أوسع من معيار آسكي ASCII الأميركي، مثل معيار Shift JIS المُستخدم في اليابان. يعرّف المعيار الجديد 96 محرفًا تمثِّل أبجدية لغة سي (المتطرّق لها في مقال أبجدية لغة C)، وفي حال كان نظامك يستخدم مجموعة محارف موسّعة extended، فسيكون المكان الوحيد الذي قد تستخدمها هو بداخل سلسلة نصية، أو متغيرات من نوع محرف، أو ضمن التعليقات وأسماء ملفات الترويسة Header files. ينبغي عليك تفقُّد ملفات توثيق نظامك إذا أردت استخدام ميزة دعم المحارف الموسّعة. دالة main يحتوي المثال 1.1 على دالتين، هما: دالة show_message ودالة main؛ فإذا صرفنا النظر عن طول دالة main موازنةً مع الدالة show_message، فسنلاحظ أن الدالتين مبنيتان بالشكل نفسه، إذ تحتوي كلا الدالتين على اسمٍ وقوسين () متبوعين بقوس معقوص، وتعليمةٍ مركبة محتواة داخل القوسين المعقوصين تتبٍّع تعريف الدالة. على الرغم من أن الدالة المركبة مختلفةٌ عن الدالة الأخرى، إلا أنك ستجد قوس الإغلاق المعقوص نفسه { الذي يتماشى مع القوس الأول }. يُعد هذا المثال دالةً حقيقيّةً يمكن استخدامها في التطبيقات الواقعية، إذ تحتوي على عدة تعليمات ضمن متن الدالة بدلًا من تعليمة واحدة في الدالة السابقة، ولربما لاحظت أيضًا أن الدالة غير مصرّح عنها باستخدام الكلمة void، لأن الدالة في الحقيقة تمرّر قيمة معينة، ولكننا سنتكلم عنها لاحقًا. الشيء المميز بخصوص دالة main هو كونها أول دالة تُستدعى عند تنفيذ البرنامج في بيئة مستضافة، إذ تستدعي لغة سي الدالة main أوّلًا عند تشغيل البرنامج، وهذا هو السبب في تسمية الدالة بهذا الاسم (main تعني رئيس)، وعلى هذا فهي دالة هامة، ولكن محتوى الدالة هام بقدر مساوٍ، وكما ذكرنا سابقًا يمكن أن تحتوي الدالة على عدّة تعليمات بداخل التعليمة المركبة، دعونا نأخذ نظرة على هذه التعليمات. التصريح التعليمة الأولى هي: int count; وهي ليست تعليمة لتنفيذ أمرٍ ما، بل تعلن عن متغيرٍ جديد ضمن البرنامج، واسمه count من نوع "عدد صحيح integer" والكلمة التي تحدد هذا النوع من المتغيرات ضمن برنامج سي مُختصرة إلى الكلمة int. لا تدل جميع أسماء أنواع المتغيرات على نوعها بوضوح في لغة سي، إذ يُستخدم اسم نوع المتغير في بعض الأحيان مثل كلمةٍ مفتاحية مختصرة وفي أحيان أخرى يُكتب كاملًا. لحسن الحظ يمكن تخمين نوع البيانات للكلمة int بقراءتها. بفضل هذه التعليمة، يعلم المصرّف الآن أن هناك متغيّرًا باسم count لتخزين قيم الأعداد الصحيحة. يجب التصريح عن جميع المتغيرات قبل استخدامها في لغة سي، على نقيض لغة FORTRAN، التي يجب أن يأتي التصريح قبل استخدام المتغير ضمن أي تعليمة. تعليمة الإسناد بالانتقال إلى السطور التالية، نلاحظ تعليمة الإسناد assignment statement، وهي التعليمة التي أسندت أول قيمة للمتغير count (القيمة هي صفر في حالتنا هذه). كانت قيمة المتغير count قبل تعليمة الإسناد غير معرّفة undefined وغير آمنة الاستخدام، وربما تتفاجئ بحقيقة أن رمز الإسناد -أي عامل الإسناد assignment operator- يُمثَّل بإشارة مساواة واحدة =، وهذا التمثيل مستخدمٌ في معظم لغات البرمجة الحديثة، ولكنه ليس بمشكلة كبيرة. إذًا، بالوصول لهذه النقطة في برنامجنا نكون قد صرّحنا عن متغير وأسندنا له قيمة الصفر، ماذا بعد؟ تعليمة الحلقة التكرارية While سنتكلم عن واحدة من تعليمات التحكم بالحلقات، ألا وهي تعليمة while لنلقي نظرةً على شكلها العام: while(expression) statement هل هذا كل ما هنالك؟ نعم، هذه بنية تعليمة while، الجزء التالي في مثالنا هو: count < 10 ويدعى باسم التعبير العلائقي relational expression، وهو مثال عن إحدى التعبيرات الصالحة الممكن استخدامها في جملة الحلقة التكرارية -مكان expression- ويُتبَع التعبير بتعليمة مركّبة، وهي مثال عن التعليمات الصالحة أيضًا الممكن استخدامها -مكان statement-. وهكذا، نستطيع تشكيل تعليمة while صحيحة. إذا برمجت برنامجًا في السابق، فستلاحظ أن متن الحلقة سينفَّذ بصورةٍ متكررة طالما أن التعبير count < 10 صحيح؛ وإذا أردنا برمجة حلقةٍ منتهية، فعلينا كتابة تعليمة ما تتسبّب بجعل التعبير السابق خاطئًا في مرحلة من المراحل، وهذا موجود في مثالنا فعلًا. هناك تعليمتان فقط داخل متن الحلقة، إذ تمثِّل الأولى استدعاءً لدالة show_message وتُكتب تعليمة استدعاء الدالة منتهيةً بقوسين () تحتويان ضمنهما لائحة الوسطاء؛ فإذا لم تحتوي الدالة على أي وسيط، نكتب قوسين فارغين؛ وإذا احتوت على وسيط أو عدة وسطاء نكتب ذلك ضمن القوسين على النحو التالي: /* call a function with several arguments */ function_name(first_arg, second_arg, third_arg); استدعاء الدالة printf هو مثال آخر، وسنشرح المزيد عن هذا الموضوع في جزئية لاحقة من هذه السلسلة. تمثّل التعليمة الأخيرة ضمن الحلقة تعليمة إسناد تضيف الرقم واحد إلى قيمة المتغير count، لكي نتوصل في نقطة ما إلى قيمة تجعل من التعبير الخاص بالحلقة خاطئًا. تعليمة الإعادة return التعليمة الأخيرة المتبقية في المثال هي تعليمة return، وتبدو للوهلة الأولى أنها استدعاءٌ لدالة ٍما، ولكن التعليمة تُكتب على النحو التالي: return expression; والتعبير expression اختياري، إذ يتبِّع مثالنا تنسيقًا شائعًا ألا وهو وضع التعبير ضمن قوسين، ولكن الأقواس غير ضرورية ولا تؤثر على عمل التعليمة. تعيد تعليمة return قيمةً من الدالة الحالية الواقعة ضمنها إلى مستدعي caller الدالة؛ فإذا لم تُزوَّد التعليمة بالتعبير expression فستعاد قيمةٌ غير معروفة unknown إلى المستدعي، وهو أمرٌ خاطئ، عدا حالة إعادة void. الدالة main غير مصرّح بنوع خاص بها على الإطلاق على عكس دالة show_message، فأيّ نوع من القيم تعيدها هذه الدالة؟ الإجابة هي int، إذ تفترض لغة سي نوع البيانات المُعاد int في حالة عدم تخصيص النوع بوضوح، لذا من الشائع أن تجد الدالة main مكتوبةً بهذه الطريقة: int main(){ وهو تعبير مماثل لما ورد في مثالنا، ويعطي النتائج نفسها، لكنك لا تستطيع استعمال هذه الميزة للحصول على نوع اعتيادي للمتغيرات بالطريقة نفسها، ويجب أن تصرِّح عن نوعها بوضوح. لكن ما الذي تعني القيمة المُعادة من الدالة main وإلى أين تُرسل؟ كانت القيمة تُرسل إلى نظام التشغيل أو الجهة التي شغّلت البرنامج في لغة سي القديمة، وفي البيئات المشابهة لنظام يونيكس UNIX، كانت القيمة "0" تعني "نجاح" العملية، بينما يدلّ على "فشل" العملية أي رقم آخر (غالبًا "1-"). حافظ المعيار عند قدومه على هذا التقليد، إذ يدل "0" على التنفيذ الناجح للبرنامج، ولكن هذا لا يعني أن القيمة "0" تُمرّر إلى البيئة المُضيفة، بل تُمرّر القيمة المناسبة للدلالة على نجاح البرنامج ضمن هذا النظام. يسبب هذا الكثير من الالتباس، لذا قد تحبذ استخدام القيمتين المعرّفتين "EXIT_SUCCESS" و"EXIT_FAILURE" ضمن ملف الترويسة <stdlib.h>. يماثل استخدام التعليمة return ضمن الدالة main استخدام الدالة exit مزوّدًا بالقيمة المُعادة وسيطًا، والفرق هنا أن الدالة exit قد تُستدعى من أي مكان ضمن البرنامج، وأن توقف البرنامج في النقطة التي استُدعيت فيها بعد إنجاز بعض العمليات النهائية. إذا أردت استخدام الدالة exit، يجب عليك تضمين ملف الترويسة <stdlib.h>، ومن هذه اللحظة فصاعدًا سنستخدم exit بدلًا من استخدام return ضمن الدالة main. الملخص تُعيد الدالة main قيمةً من نوع int. يُماثل استخدام التعليمة return ضمن الدالة main استدعاء الدالة exit، الفرق هنا أنه من الممكن استدعاء exit في أي نقطة ضمن البرنامج. إعادة القيمة 0 أو EXIT_SUCCESS يعني نجاح البرنامج، بينما تُعد أي قيمة أخرى فشلًا للبرنامج. مع أنّ المثال المُناقش قصير، إلا أنه سمح لنا بمناقشة العديد من مزايا اللغة المهمة، وهي: بنية البرنامج Program structure. التعليق Comment. تضمين الملفات File inclusion. تعريف الدوال Function definition. التعليمات المركبة Compound statements. استدعاء الدوال Function calling. التصريح عن المتغيرات Variable declaration. العمليات الحسابية Arithmetic. الحلقات التكرارية Looping. لكننا طبعًا لم نناقش هذه المواضيع بتعمّق بعد. ترجمة -وبتصرف- لقسم من الفصل Chapter 1 An Introduction to C من كتاب The C Book. اقرأ أيضًا المقال التالي: بعض البرامج البسيطة بلغة سي C: المصفوفات والعمليات الحسابية إدارة الذاكرة (Memory management) في لغة C المتغيرات الشرطية وحلها مشاكل التزامن بين العمليات في لغة C متغيرات تقييد الوصول (Semaphores) في لغة البرمجة سي C
-
تُعَد الحاويات Containers من التطبيقات الثورية المعاصرة، إذ أنك ستستخدمها بغضّ النظر عن البرنامج الذي تعمل عليه سواءً كان كوبيرنيتيس Kubernetes، أو دوكر Docker، أو نظام كور CoreOS، أو سيلفربلو Sliverblue، أو فلاتباك Flatpak؛ إذ تستخدم جميع البرامج المذكورة سابقًا الحاويات بصورةٍ أساسية بفضل سهولة التعامل معها وأمانها العالي وقابليّة توسعتها. قد تكون الحاويات صعبة الفهم للبعض، فما الذي نعنيه عندما نقول أنّ النظام يعمل بداخل حاوية؟ وكيفَ لعمليّة محتواة داخل حاوية ما أن تتفاعل مع بقيّة أجزاء الجهاز المضيف الذي تعمل عليه؟ لا أحد يحبّ الغموض، لذا سنشرح في هذه المقالة تقنية عمل الحاويات. مجالات الأسماء تُعَد مجالات الاسماء Namespaces من أكثر المصطلحات شيوعًا في عالم البرمجة؛ وإذا كنتَ مبرمجًا، فعلى الأغلب أنك رأيت سطرًا برمجيًّا مشابهًا لهذا: ;using namespace std أو لربّما رأيت هذا السطر ضمن ملف XML: <book xmlns="http://docbook.org/ns/docbook" xml:lang="en"> يوفر هذا النوع من الأسطر البرمجية توصيفًا لسياق الأوامر الموجودة في ملف الشيفرة المصدرية التي تليه؛ فعلى سبيل المثال، السبب الوحيد في كون الكلمة المفتاحية cout ذات معنى للغة ++C، هو وجود مجال اسم يصف عملها. إذا كان الشرح السابق يستخدم كثيرًا من المصطلحات التقنية ولم تفهمه، دعنا نسقط الأمر على الحياة الواقعية؛ فنحن نستخدم مجالات الاسم أيضًا في حياتنا اليومية، صحيح نحن لا ندعوها باسمها بالطبع، ولكننا نستخدم المفهوم طوال الوقت؛ فإذا قلت مثلًا، "أنا من أكبر معجبي هذه السلسلة"، فقد تقصد سلسلة شركات أو محال تجاريّة معيّنة في سياق مجال الأعمال، أو سلسلة أفلام شهيرة في سياق السينما؛ كما قد تعني الجملة "على أيّ محرك يعمل هذا الشيء؟" شيئًا ما بداخل ورشة الميكانيكي، وشيئًا آخر في مجال تطوير الويب. نحن بالطبع لا نحدّد السياق خلال محادثتنا مع بعضنا بعضًا، إذ إنّ أدمغتنا ذكيةً كفاية لمعرفة السياق لوحدها، بينما نحتاج لإعلان مجال الاسم وتحديده عندما نتعامل مع الحواسيب. بالنسبة للحاويات، يصف مجال الاسم الحدود التي تستطيع الحاوية "رؤية" العمليات التي تجري بداخلها. أمر lsns لا يعي البعض هذا الأمر، ولكن يعمل نظام لينكس على إدارة العديد من مجالات الاسم المخصصة لإنجاز مختلف العمليات. وتستطيع الاطلاع على مجالات الاسم الموجودة على نظامك باستخدام آخر إصدارات حزمة util-linux: lsns $ NS TYPE NPROCS PID USER COMMAND 4026531835 cgroup 85 1571 seth /usr/lib/systemd/systemd --user 4026531836 pid 85 1571 seth /usr/lib/systemd/systemd --user 4026531837 user 80 1571 seth /usr/lib/systemd/systemd --user 4026532601 user 1 6266 seth /usr/lib64/firefox/firefox [...] 4026532928 net 1 7164 seth /usr/lib64/firefox/firefox [...] [...] إذا لم يحتوي إصدار الحزمة util-linux على أمر lsns، فيمكنك رؤية سجل مجالات الاسم ضمن المسار proc/: ls /proc/*/ns $ 1571 6266 7164 [...] ls /proc/6266/ns $ [...] ipc net pid user uts يجري عدُّ كل عملية Process في نظام لينكس برقمٍ تسلسلي يُدعى معرّف العملية process ID -أو اختصارًا PID-، ويرتبط كل PID بمجال اسمٍ معيّن. ويمكن لمعرّفات PIDs التي تحمل مجال الاسم نفسه الوصول لبعضها بعضًا، نظرًا لأنها مبرمجة ضمن مجال الاسم نفسه؛ بينما لا تستطيع العمليات التي تنتمي لمجالات أسماء مختلفة التفاعل والتواصل مع بعضها بعضًا بصورةٍ اعتيادية، لأنها تعمل ضمن سياقات مختلفة، أي مجالات اسم مختلفة؛ وهذا هو السبب في كون عمليات التي تعمل ضمن حاوية ما غير قادرةٍ على الوصول للمعلومات الموجودة خارج الحاوية، أو المعلومات الموجودة ضمن حاويةٍ أخرى مختلفة. إنشاء مجال اسم جديد تقدّم أي برمجيةٍ تتعامل مع الحاويات إدارةً تلقائية لمجالات الاسم في الحالة الاعتيادية؛ فعندما ينشئ المستخدم تطبيقًا أو بيئةً محتواةً بداخل حاويةٍ ما، عندها لا يلزم عليه استخدام الأمر lsns لتفقّد مجالات الاسم الموجودة وإنشاء مجال جديد يدويًّا، إذ تتولّى هذه البرمجيات المهمّة المذكورة تلقائيًّا بالاعتماد على مجالات اسم كل عمليّة PID، وبمساعدة نواة لينكس؛ إلّا أنّه من الممكن محاكاة هذه المهمة يدويًّا للحصول على فهم أفضل لكيفية عملها، وما الذي يحصل خلف الكواليس. أولًا، عليك تعريف عملية لا تعمل حاليًّا على جهازك، وفي هذا المثال، سنستخدم صدفة Z -أو اختصارًا Zsh-، لأنّ الجهاز الحالي الذي نعمل عليه يستخدم Bash؛ فإذا كان جهازك يعمل على Zsh، فبإمكانك استخدام Bash، أو tcsh، أو أيّ صدفة Shell غير فعالة حاليًّا. يُعَد هدفنا هنا هو إثبات أنّ الصدفة المُختارة (العملية) غير فعالة، إذ يمكننا التحقق من ذلك عن طريق استخدام الأمر pidof، الذي يطلب من النظام العثور على PID لأي تطبيق تكتبه بعد الأمر: pidof zsh $ sudo pidof zsh $ إذا لم يُعِد هذا الأمر أيّ رقم PID بالنتيجة، فهذا يعني أن التطبيق المُدخل غير فعال. أمر unshare يشغّل الأمر unshare أي تطبيق ضمن مجال اسم غير مشترك مع عمليات الأصل، وهناك الكثير من أنواع مجالات الاسم المختلفة التي يتيحها هذا الأمر. وبإمكانك الاطلاع على صفحة الدليل man للتعرف على الخيارات المتاحة. لإنشاء مجال اسم جديد خاص بالأمر الذي ستجرّبه: sudo unshare --fork --pid --mount-proc zsh $ % ستعمل صدفة Zsh في مجال اسمها عند التشغيل تلقائيًا، وذلك بحكم أنها صدفةٌ تفاعلية. ولكن مع ذلك، لا ينطبق هذا الأمر على جميع العمليات؛ إذ تعمل بعضها في الخلفية تاركةً إياك في واجهة ضمن مجال اسمها الأصلي. يمكنك ملاحظة أنك تركت مجال الاسم المعتاد بالنظر إلى المعرّف PID الخاص بعمليتك الفرعية الجديدة، وذلك طالما بقيت في جلسة Zsh نفسها: pidof zsh % pid 1 إذا كنت تعرف ولو الشيء اليسير عن أرقام عمليات لينكس التعريفية، فأنت تعلم أن PID ذو القيمة 1 محجوزٌ دائمًا؛ وذلك بسبب طبيعة عملية بدء تشغيل النظام boot (تطبيق systemd على معظم التوزيعات عدا سلاك وير Slackware وديفوان Devuan وبعض الحزم المخصّصة من توزيعة آرش Arch)، إذ من المستحيل أن تكون قيمة PID الخاصة بـ Zsh أو أي تطبيق آخر لا ينتمي لمجموعة تطبيقات بدء تشغيل النظام مساويةً 1، فمن غير الممكن لحاسوبك أن يعمل ويشغل النظام دون وجود عمليّة إقلاع تبدأه، وعلى الرغم من ذلك، تعرض لك الصدفة Zsh أن قيمة العملية بها تشغل القيمة 1. بغض النظر عن المعلومة التي تخبرك بها الصدفة، فالقيمة 1 لم تُستبدل، وتستطيع التأكد من ذلك عن طريق فتح نافذة طرفية جديدة والبحث عن العملية ذات القيمة 1، على النحو التالي: $ ps 1 init تستطيع البحث عن قيمة PID الخاصة بالصدفة Zsh عن طريق: pidof zsh $ 7723 كما تلاحظ من الأوامر السابقة، يعي نظامك المضيف ما الذي يجري، ويعرف أن قيمة PID للصدفة Zsh ليست 1 (لن تكون نتيجة الأمر 7723 إن جرّبتها، وإذا ساوتها فسيكون ذلك وليد الصُدفة). تعتقد الصدفة Zsh أن رقم عمليّتها PID هو 1 بسبب نطاقها Scope المغلق أو المحتوى بداخل مجال الاسم، وبمجرّد إنشاء عملياتٍ فرعية في مجال الاسم هذا، فستبدأ أرقام عمليّاتها بدءًا من الواحد، ولكن في سياق مجال الاسم وحده. تُعَد مجالات الاسم بالإضافة لتقنيات أخرى مثل cgroups وغيرها، أساسًا لمفهوم الحاويات. يساعد فهم فكرة أن مجالات الاسم الفرعية موجودةً داخل سياق مجالات الاسم العامة ضمن بيئة المضيف (يُمثَّل المضيف بالحاسوب في أمثلتنا السابقة، ولكن في التطبيقات الواقعية قد يكون خادمًا أو سحابة هجينة Hybird cloud)، في فهم كيف ولماذا تعمل برامج الحاويات على النحو الذي تعمل به، إذ لا "تعلم" حاوية تشغِّل مدونة ووردبريس Wordpress مثلًا، أنها تعمل ضمن حاوية، بل تعلم أن لها وصولًا لنواة النظام Kernel وقِسمًا من الذاكرة العشوائيّة RAM، بالإضافة لأيٍّ من ملفات الإعدادات Configuration التي زوّدتها؛ لكنها لا تستطيع الوصول إلى المسار الرئيسي Home أو أي مسارات لم تعطها الإذن للوصول إليها. إضافةًً لذلك، لا يمكن للعمليات التي تعمل داخل سياق المدوّنة التأثير على أيٍّ من العمليات الأخرى الموجودة بداخل النظام، وذلك لعدم شمول مجموعة أرقام العمليات -على حدّ علمها- أي عملية قبل 1؛ و1 هي الحاوية التي تعمل بداخلها. تُعَد الحاويات ميزةً قويةً في نظام لينكس، وتتزايد شهرةً يومًا بعد يوم. والآن بما أنك تعلمت كيف تعمل هذه التقنية، جرّب التعامل معها باستخدام بعض الأنظمة المخصصة، مثل كوبيرنيتيس Kubernetes، أو سيلفربلو Silverblue، أو فلاتباك Flatpak، واكتشف بعضًا من إمكانياتها. تعمل الحاويات في نهاية المطاف مثل نظام لينكس، لذا طبّق ما تعرفه عن لينكس وكيفية تثبيته باستخدامها. ترجمة -وبتصرف- للمقال Demystifying namespaces and containers in Linux لصاحبه Seth Kenlon. اقرأ أيضًا كيفية تأمين الحاويات عن طريق سي لينكس SELinux دليل لفهم حاويات لينكس وكيفية التعامل معها بدء تشغيل واستعمال حاويات لينكس (LXC) إجراء مهام معينة عند مختلف مراحل دورة حياة حاويات لينكس (LXC)
-
يعطّل معظم الأشخاص التأمين المُعزَّز في لينكس Security-Enhanced Linux -أو اختصارًا سي لينوكس SELinux- إذا واجهتهم أي مشكلة خلال عملهم على بيئة لينكس، إذ يبدو أن تعطيلها يحل المشاكل بصورةٍ مفاجئة، لكن هذا يتسبب بخسارتهم لأداة حماية فائقة القوّة والأهميّة. تتزايد التهديدات الأمنية مع تزايد استخدام الحاويات Containers والخدمات المصغرة Microservices ومعمارية الحوسبة الموزّعة Distributed Architecture، وذلك بسبب مشكلة قديمة ومعروفة جيّدًا، ألا وهي: التسارع velocity؛ إذ تتميز الحاويات بمنحك القدرة على التحرك بسرعة وإنجاز المزيد والتغير بسرعة أيضًا. وهذا يعني أن تطبيق الحاويات انتشر بصورةٍ كبيرة، ولكن سرعة تطبيقها محدودة. وستواجهك الكثير من المشاكل ونقاط الضعف خلال استخدامها؛ فهذا أمرٌ طبيعي عندما تسعى لإنجاز الأمور على نحوٍ أسرع. كيف تخفف من التهديدات؟ يقول سن تزو: "المحارب الحكيم يتفادى المعركة". ينطبق هذا الاقتباس على عملية حماية الحاويات بصورةٍ مثالية؛ إذ لتفادي المشكلات (المعارك)، عليك التأكد أن مضيف الحاوية آمن ويستخدَم سي لينكس مثل خط دفاعٍ أوّل. أُطلِق سي لينكس على أنه مشروعٌ مفتوح المصدر في عام 2000، ودُمج مع نواة لينكس في عام 2003. ووفقًا لموقع Red Hat's explainer، فإن سي لينكس هو معماريّة أمان لأنظمة لينكس، تسمح لمسؤولي النظام بالتحكم بمن لديه صلاحية الولوج للنظام، وقد طوِّرت البرمجية من قبل وكالة الأمن القومي United States National Security Agency -أو اختصارًا NSA-، بكونها سلسلةً ضمن مجموعةٍ من الرُقع Patches لـ نواة لينكس باستخدام وحدات أمن لينكس Linux Security Modules -أو اختصارًا LSM". البداية عندما تسمع كلمة حاويات، سيتبادر لذهنك أولًا دوكر Docker، فقد بدأ دوكر بثورةٍ ساهمت بتبنٍ واسعٍ لتقنية الحاويات بعد ظهوره في عام 2013؛ فهو من أبرز الأسباب التي ساهمت بانتشار شعبية الحاويات. وكما ذكرنا سابقًا، ساهم تزايد معدل تبنّي هذه التقنية بتزايد معدل الثغرات الأمنية. قبل البدء بعملية تأمين حاويات دوكر باستخدام سي لينكس، عليك تجهيز بعض الأشياء. المتطلبات نظام تشغيل سينت أو إس CentOS إصدار 8/RHEL مُثبت ومهيّأ. دوكر Docker CE مُثبَّت ومهيّأ. حسابين مستخدم، إحداها بصلاحيات الجذر root والآخر بدون؛ وهو موجود باسم mcalizo في الأمثلة ضمن هذه المقالة. يمكنك اتباع التعليمات، إذا أردت تثبيت دوكر على خادم CentOS RHEL/8؛ وإذا كنت تعمل على RHEL 8، فعليك إزالة الحزمتين المثبّتتين تلقائيًّا Podman وrunc قبل البدء. دعنا نتأكد أوّلًا من أن سي لينكس يعمل: mcalizo@Rhel82 ~]$ sestatus] SELinux status: enabled SELinuxfs mount: /sys/fs/selinux SELinux root directory: /etc/selinux Loaded policy name: targeted Current mode: enforcing Mode from config file: enforcing Policy MLS status: enabled Policy deny_unknown status: allowed (Memory protection checking: actual (secure Max kernel policy version: 31 $ [ ~ mcalizo@Rhel82] من ثم، تأكد أن إصدار نظام التشغيل الخاص بك ودوكر يعملان. سجّل الدخول مثل مستخدمٍ بصلاحيات الجذر، ونفّذ التعليمات التالية: root@rhel82 ~]# cat /etc/redhat-release] (Red Hat Enterprise Linux release 8.2 (Ootpa [root@rhel82 ~]# root@rhel82 ~]# systemctl status docker] docker.service - Docker Application Container Engine ● (Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled Active: active (running) since Wed 2020-10-28 19:10:14 EDT; 15s ago Docs: https://docs.docker.com (Main PID: 30768 (dockerd Tasks: 8 Memory: 39.0M CGroup: /system.slice/docker.service └─ 30768 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock Oct 28 19:10:13 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:13.889602941-04:00" level=error msg="> Oct 28 19:10:13 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:13.903413613-04:00" level=warning msg> Oct 28 19:10:13 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:13.903427451-04:00" level=warning msg> Oct 28 19:10:13 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:13.903538271-04:00" level=info msg="L> Oct 28 19:10:14 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:14.132060506-04:00" level=info msg="D> Oct 28 19:10:14 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:14.308943088-04:00" level=info msg="L> Oct 28 19:10:14 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:14.319438549-04:00" level=info msg="D> Oct 28 19:10:14 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:14.319570298-04:00" level=info msg="D> Oct 28 19:10:14 rhel82.home.labs.com dockerd[30768]: time="2020-10-28T19:10:14.333419209-04:00" level=info msg="A> Oct 28 19:10:14 rhel82.home.labs.com systemd[1]: Started Docker Application Container Engine تحقق من إصدار دوكر لديك: root@rhel82 ~]# docker --version] Docker version 19.03.13, build 4484c46d9d اخترق المضيف أفضل الطرق للتعلم هي خوض التجربة، لذلك سنبيّن في هذه الفقرة سهولة اختراق مضيف دوكر باستخدام شيفرة حقن Injection خبيثة، إذا لم يكن أمن الحاوية مُعدًّا على النحو الصحيح. لكي تكون قادرًا على إجراء هجومٍ ما على المضيف، يجب أن يكون المستخدم الخبيث الذي لا يملك صلاحيات الجذر (باسم mcalizo في أمثلتنا) منتميًا للمجموعة التي أنشأت حاويات دوكر. وللتأكد من ذلك، نتفقّد المجموعة التي ينتمي إليها الحساب mcalizo عن طريق: root@Rhel82 ~]# groups mcalizo] mcalizo : mcalizo تُظهِر رسالة الخَرج أن الحساب mcalizo ينتمي لمجموعته الخاصة، وهذا يعني أنه لا يمكنه إنشاء حاوية دوكر. وإذا حاولت إنشاء حاوية دوكر باستخدام الحساب سيظهر الخطأ التالي: mcalizo@Rhel82 ~]$ docker run -it --rm centos:latest /bin/sh] docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.40/containers/create: dial unix /var/run/docker.sock: connect: permission denied. See 'docker run --help'. للسماح لحساب mcalizo بإنشاء الحاوية، أضِف المستخدم لمجموعة docker: root@Rhel82 ~]# usermod -G docker -a mcalizo] root@Rhel82 ~]# groups mcalizo] mcalizo : mcalizo docker ثم أنشئ حاويةً من نوع fedora:latest وادخل إليها لتكتشفها: mcalizo@Rhel82 ~]$ docker run -it --rm fedora:latest /bin/sh] Unable to find image 'fedora:latest' locally latest: Pulling from library/fedora ee7e89337106: Pull complete Digest: sha256:b9ec86d36fca7b1d3de39cd7c258e8d90c377d312c21a7748071ce49069b8db4 Status: Downloaded newer image for fedora:latest sh-5.0# cat /etc/redhat-release (Fedora release 33 (Thirty Three ستُسجَّل مثل مستخدم بصلاحيات الجذر تلقائيًّا، عندما تسجل الدخول لحاوية منشأة جديدًا: sh-5.0# whoami root sh-5.0# تستطيع فعل أي شيء بداخل الحاوية بصفتك مستخدمًا بصلاحيات الجذر؛ وهذا يعني أنك تستطيع إساءة استخدام مضيف الحاوية وتتسبّب بكثيرٍ من الضرر. تعني قدرتك على إنشاء الحاويات أنّك تستطيع فعل أي شيء للمضيف، حتى إن لم تكن جزءًا من المستخدمين بصلاحيات الجذر على الجهاز المضيف. لتوضيح الثغرة الأمنية هذه، أُخرج من الحاوية التي أنشأتها وأنشئ حاويةً جديدة: mcalizo@Rhel82 ~]$ docker run -it --rm -v /:/exploit fedora:latest /bin/bash] #[root@131043f2e306 /] ينقل الخيار -v مسار المضيف الرئيس الخاص بدوكر / إلى مسار الحاوية المدعوّ exploit/: /root@131043f2e306 /]#ls exploit] bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var بسبب عملية النقل هذه، تستطيع فعل أي شيء على جهاز دوكر المضيف؛ إذ يمكنك مثلًا حذف الملفات، أو تعديل ملفات الضبط بهدف إلحاق الضرر بالنظام، أو تثبيت تطبيق حصان طروادة أو برمجيات خبيثة مشابهة لسرقة البيانات المهمة. سبب هذه الثغرة لعلك تتسائل عن سبب حدوث ذلك بالرغم من أن سي لينكس مفعّل، إذ نستطيع فهم ما السبب بالتحديد، من خلال النظر الدقيق لكيفية عمل سي لينكس. تأكد من أن سي لينكس يحتوي على سياق دوكر Docker Context: mcalizo@Rhel82 ~]$ ps -eZ | grep docker] system_u:system_r:container_runtime_t:s0 30768 ? 00:00:04 dockerd $[~ mcalizo@Rhel82] يحتوي سي لينكس على السياق كما توقعنا؛ وهذا يعني أن سي لينكس مسؤولٌ عن إدارة عفريت دوكر Docker daemon. افحص عفريت دوكر للتحقُّق مما إذا كان سي لينكس مفعّلًا تلقائيًا: mcalizo@Rhel82 ~]$ docker info | grep Security -A3] :Security Options seccomp Profile: default Kernel Version: 4.18.0-193.el8.x86_64 كما نلاحظ، سي لينكس غير مفعَّل تلقائيًا، بالتالي لقد وجدنا المشكلة، وكل ما علينا فعله هو تفعيل سي لينكس لسد الثغرة؛ بحيث يتحكّم ويدير دوكر. ولتفعيل سي لينكس، أنشئ ملفًّا في المسار etc/docker/daemon.json كما هو موضح هنا، ولكن يجب أن تمتلك صلاحيات الجذر لتفعيله: root@Rhel82 ~]# cat /etc/docker/daemon.json] { "selinux-enabled": true } #[~ root@Rhel82] root@Rhel82 ~]# systemctl restart docker] بعد إنشائك للملف أو تحديثه، وإعادة تشغيل دوكر؛ يجب أن ترى أن سي لينكس مفعّل في عفريت دوكر: root@Rhel82 ~]# systemctl restart docker] mcalizo@Rhel82 root]$ docker info | grep Security -A3] :Security Options seccomp Profile: default selinux [mcalizo@Rhel82 root]$ على الرغم من أنّ وصل مسارات ملفات filesystem معيّنة لمضيف دوكر داخل حاوية دوكر ما زال ممكنًا، إلّا أنّ تحديث الملفات أو الوصول إليها أصبح غير متاحًا الآن: mcalizo@Rhel82 root]$ docker run -it --rm -v /:/exploit fedora:latest /bin/bash] root@ecb5836da1f6 /]# touch /exploit/etc/shadow.sh] touch: cannot touch '/exploit/etc/shadow.sh': Permission denied #[/ root@ecb5836da1f6] تعتمد درجة أمان حاويتك على قوة أمان نظام تشغيل مضيف الحاوية. وهناك كثيرٌ من الوسائل لتأمين نظام لينكس، والعديد منها متاحٌ بهدف زيادة أمان نظامك. يتواجد سي لينكس افتراضيًا مع توزيعات لينكس ليضيف طبقةً إضافية من الحماية؛ فتأكد من تفعيله وعمله صحيحًا للاستفادة منه ومن الحماية التي يقدمها لنظامك. ترجمة -وبتصرف- للمقال Secure your containers with SELinux لصاحبه Mike Calizo. اقرأ أيضًا بدء تشغيل واستعمال حاويات لينكس (LXC) التعامل مع حاويات Docker إجراء مهام معينة عند مختلف مراحل دورة حياة حاويات لينكس (LXC) مدخل إلى حاويات لينكس LXC
-
هل يمكنك الحصول على حاويات لينكس Linux Containers دون استخدام Docker أو OpebShift أو Kubernetes؟ نعم، يمكنك، فقد طوّر مشروع LXC مفهوم تشغيل نظام تشغيل افتراضي يعمل على نواة النظام، ومُحتوًى ضمن مجموعةٍ من التعليمات المعرفة مسبقًا، وذلك قبل سنوات عدّة من ظهور Docker وانتشار مصطلح الحاويات. في الحقيقة، بُنيَ Docker على مشروع LXC، وتتوافر اليوم كثيرٌ من المنصات التي تُبرِز مزايا LXC بصورةٍ مباشرة أو غير مباشرة، كما تبسّط معظم هذه المنصات عملية إنشاء وإدارة الحاويات بصورةٍ ملحوظة، ومن المنطقي أن تُستخدم هذه الأدوات المتخصصة في مشروعات كبيرة، لكن لا تنطبق هذه الحالة على الجميع، إذ من المحتمل ألا تدير مشروعًا كبيرًا، وألا تكون لديك الرغبة بالوصول إلى خدمات كبيرة وتعلّم أدوات مخصّصة لإنشاء الحاويات. لحسن الحظ، تستطيع إنشاء واستخدام وتعلّم الحاويات باستخدام حاسوبٍ يعمل على نظام لينكس ضمن هذا الدليل؛ إذ سنساعدك في فهم الحاويات باستخدام LXC، وكيفية عملها، ولماذا تعمل، وكيف تصحّح الأخطاء إذا واجهت أي مشكلات. تثبيت LXC إذا كنتَ تبحث عن مصدر سريع لفهم مشروع LXC، فزُر موقع المشروع الرسمي. إذا لم يكن LXC مثبتًا بعد على جهازك، فثبّته باستخدام مدير الحزم على لينكس. وبالنسبة لتوزيعة فيدورا Fedora والتوزيعات المشابهة، اكتب: sudo dnf install lxc lxc-templates lxc-doc $ أما بالنسبة لتوزيعتي ديبيان Debian وأوبنتو Ubuntu والتوزيعات المشابهة، فاكتب: sudo apt install lxc $ إنشاء جسر للشبكة تفترض معظم الحاويات توافر اتّصال بالشبكة، كما تفترض معظم أدوات إدارة الحاويات أن المستخدم سينشئ شبكات افتراضية. يُعد جسر الشبكة network bridge من المكونات الأساسية في أيّ حاوية، وهو يكافئ إلى حدٍ ما مبدّل switch شبكة، إذ يعمل بمثابة مأخذ ذكي ذي مخرجين Y-Adapter، مثل مأخذ سماعات الرأس الذي يسمح لك ولصديقك بالاستماع للصوت في الوقت ذاته باستخدام سماعتين منفصلتين، والفرق هنا هو أنّ الإشارة التي تُنقل هي حزم البيانات وليس إشارة الصوت. يمكنك إنشاء جسر للشبكة برمجيًّا، لتمكين كلٍّ من حاسوبك المضيف والنظام المحتوى من إرسال واستقبال البيانات عبر الشبكة باستخدام جهاز اتصالٍ واحد، مثل منفذ الإيثرنت Ethernet، أو شريحة الاتصال اللاسلكية. يُهمل هذا الجانب الأساسي غالبًا عند استخدامك لبرنامج يولّد حاويات النظام تلقائيًا، وذلك رغم أهميته؛ إذ من المستبعد أن تخصّص جهاز اتصال فيزيائي لكل حاوية تشغّلها. وبالتالي يجب أن تستوعب أنّ الحاويات تتعامل مع أجهزة اتصال افتراضية، وفهمك لهذا سيساهم في نجاح عملية تصحيح الأخطاء، إذا فقدت الحاوية اتّصالها بالشبكة. لإنشاء جسرٍ للشبكة على جهازك، عليك الحصول على الأذونات المطلوبة. ومن خلال تنفيذك للتعليمات في هذا الدليل، استخدم الأمر sudo الذي سيعطيك صلاحيات الجذر؛ بينما يقدم توثيق LXC طريقةً لتهيئة صلاحيات المستخدمين دون استخدام الأمر sudo. sudo ip link add br0 type bridge $ يمكن التحقق من عملية إنشاء واجهة الشبكة الافتراضية بنجاح عن طريق: sudo ip addr show br0 $ 7: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 26:fa:21:5f:cf:99 brd ff:ff:ff:ff:ff:ff بما أنّ br0 واجهة شبكة افتراضية Virtual Network Interface، فهو يتطلب عنوان بروتوكول الإنترنت الخاص به. لذلك، اختر عنوانًا محليًا صالحًا لا يتعارض مع أي عنوانٍ آخر موجود على شبكتك، واربطه مع جهاز الشبكة الافتراضية br0 على النحو التالي: sudo ip addr add 192.168.168.168 dev br0 $ تأكّد في النهاية أن br0 يعمل بنجاح من خلال الأمر التالي: sudo ip link set br0 up $ تهيئة تكوين الحاوية قد يكون ملف ضبط Config حاوية LXC معقدًا بحسب الحاجة لتهيئة الحاوية على شبكتك ونظام المضيف، ولكن سيكون ملف الضبط في مثالنا التالي بسيطًا. أنشئ ملفًا باستخدام أي محرر نصوص تفضّله، وعرّف اسمًا للحاوية واكتب إعدادات الشبكة: lxc.utsname = opensourcedotcom lxc.network.type = veth lxc.network.flags = up lxc.network.link = br0 lxc.network.hwaddr = 4a:49:43:49:79:bd lxc.network.ipv4 = 192.168.168.1/24 lxc.network.ipv6 = 2003:db8:1:0:214:1234:fe0b:3596 احفظ هذا الملف في المسار الرئيسي Home باسم mycontainer.conf، وتكون تسمية lxc.utsname اختياريّة، إذ يمكنك تسمية الحاوية كما شئت، وستستخدم هذا الاسم لتشغيل وإيقاف الحاوية. يُضبط نوع الشبكة إلى veth، وهو نوع من كابلات الإيثرنت Ethernet الافتراضية؛ إذ يتمحور مبدأ عملها في إرسال الاتصال من نوع veth من الحاوية إلى جهاز جسر الشبكة، المُعرّف بخاصية lxc.network.link، وبقيمة br0. يقع عنوان بروتوكول الإنترنت الخاص بالحاوية في شبكة جهاز التجسير نفسها، ولكنّه مختلف لتفادي أي تعارض. جميع القيم متغيّرة ويمكن تعديلها حسب الحاجة، باستثناء نوع الشبكة veth وراية الشبكة up. ويمكن استعراض لائحة الخصائص عن طريق الأمر man lxc.container.conf؛ وإذا كان الأمر مفقودًا من نظامك، تفقّد مدير الحزم للحصول على حزم توثيق LXC منفصلة. هناك كثيرٌ من الأمثلة التي يمكنك مطالعتها لاحقًا على هيئة ملفات ضبط ضمن المسار usr/share/doc/lxc/examples. تشغيل صدفة الحاوية أنجزنا أكثر من نصف العمل اللازم لإنشاء حاوية؛ فقد أنشأنا بنية الشبكة التحتية، وثبتنا شريحة الاتصال الافتراضية على حاسوبنا الافتراضي؛ وبقي الآن تثبيت نظام التشغيل الخاص بالحاوية. ولكن يمكنك رؤية حاوية LXC تعمل بتشغيل الصدفة Shell ضمن مساحة الحاوية دون تثبيت نظام التشغيل، من خلال الأمر التالي: \ sudo lxc-execute --name basic $ \ rcfile ~/mycontainer.conf /bin/bash-- logfile mycontainer.log-- # في هذه المرحلة من بناء الحاوية، ألقِ نظرةً على إعدادات الشبكة، إذ ستكون مشابهةً لتلك الموجودة في الأسفل ولكن بقيم فريدة: /usr/sbin/ip addr show # 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state [...] link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo [...] 22: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> [...] qlen 1000 link/ether 4a:49:43:49:79:bd brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.168.167/24 brd 192.168.168.255 scope global eth0 valid_lft forever preferred_lft forever inet6 2003:db8:1:0:214:1234:fe0b:3596/64 scope global valid_lft forever preferred_lft forever [...] تميِّز الحاوية بنية الشبكة المزيفة، ونواة النظام المستخدمة: uname -av # Linux opensourcedotcom 4.18.13-100.fc27.x86_64 #1 SMP Wed Oct 10 18:34:01 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux استخدم الأمر exit لمغادرة الحاوية: exit # تثبيت نظام تشغيل الحاوية تُعَد عملية بناء بيئة مُحتواة كاملة من البداية معقدةً أكثر موازنةً بمرحلة تهيئة الشبكة الافتراضية وضبطها، لذا يمكننا استخدام قوالب حاويات جاهزة من LXC. وإذا لم يحتو جهازك على أيّ قالب، فألقِ نظرةً على حزمة قوالب LXC الموجودة ضمن مستودع برمجيّتك. ويكون المسار الافتراضي لقوالب LXC هو usr/share/lxc/templates. ls -m /usr/share/lxc/templates/ $ lxc-alpine, lxc-altlinux, lxc-archlinux, lxc-busybox, lxc-centos, lxc-cirros, lxc-debian, lxc-download, lxc-fedora, lxc-gentoo, lxc-openmandriva, lxc-opensuse, lxc-oracle, lxc-plamo, lxc-slackware, lxc-sparclinux, lxc-sshd, lxc-ubuntu, lxc-ubuntu-cloud اختر قالبك المفضل، وأنشئ الحاوية. استخدمنا في مثالنا هذا قالب Slackware. sudo lxc-create --name slackware --template slackware $ ستعلّمك مشاهدة عملية تنفيذ القالب عن كثب الكثير عن كيفية عمل القوالب، وكأنّك تبني قالبًا من الصفر بيديك؛ إذ تُوصَف عملية التنفيذ بأنها واضحة ومسهبة، وتستطيع ملاحظة أن lxc-createيضبط جذر Root الحاوية للمسار var/lib/lxc/slackware/rootfs، كما يمكن تحميل وتثبيت كثيرٍ من الحزم في هذا المسار أيضًا. تمنحك قراءة ملفات القالب فهمًا أكبر لكيفية عمل شجرة الأجهزة الدنيا minimal device tree وملفات تخزين البيانات المؤقتة Spool الشائعة وملف جدول الأنظمة fstab وملفات init وغيرها، كما تمنع القوالب بعض الخدمات التي لا تحتوي على فائدة مرجوّة في الحاويات من العمل، مثل udev للتعرُّف على العتاد الصلب. ونظرًا لاحتواء القوالب على عددٍ كبيرٍ من إعدادات لينكس التقليدية، فمن الأفضل بناء القالب مع التقيُّد بقالبٍ مشابه للقالب الذي تبنيه (إذا أردت بناء قالبٍ من الصفر)، وذلك تجنّبًا للأخطاء والعثرات التي قد تحدث، مثل بعض الأخطاء التي كُشف عنها وعولجت من قِبل مشروع LXC. بعد تهيئة بيئة نظام التشغيل الأساسيّة، تستطيع بدء الحاوية من خلال الأمر التالي: \ sudo lxc-start --name slackware $ rcfile ~/mycontainer.conf-- بكتابتك للأمر السابق، تكون قد بدأت تشغيل الحاوية، ولكنك لم تربطها بعد؛ فبعكس المثال السابق، أنت لا تتعامل مع الحاوية في سطر الأوامر Shell، وإنما تتعامل مع نظام تشغيل محتوى، لهذا اربط الحاوية بالاسم، عن طريق الأمر: sudo lxc-attach --name slackware $ # تأكد أن عنوان بروتوكول الإنترنت التابع للحاوية وعنوان بروتوكول الإنترنت في ملف الضبط Config متماثلان، عن طريق: usr/sbin/ip addr SHOW | grep eth/ # 34: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 [...] 1000 link/ether 4a:49:43:49:79:bd brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.168.167/24 brd 192.168.168.255 scope global eth0 اُخرُج من الحاوية، وأغلقها عن طريق: exit # sudo lxc-stop slackware $ استخدام حاويات LXC في التطبيقات الواقعية يسهّل لنا مشروع LXC عملية إنشاء وتشغيل حاوية آمنة. تطورت الحاويات كثيرًا منذ ظهورها أول مرة بواسطة LXC في عام 2008، وننصحك بالاستفادة من خبرات مطوّريها لمصلحتك. على الرغم من وضوح توجيهات دليل LXC، إلّا أن هدفنا في هذا الدليل هو توفير فهمٍ أعمق لما يحصل خلال تنفيذ هذه الأوامر خلف الكواليس. ترجمة -وبتصرف- للمقال Behind the scenes with Linux containers لصاحبه Seth Kenlon اقرأ أيضًا إجراء مهام معينة عند مختلف مراحل دورة حياة حاويات لينكس (LXC) ألف باء أساسيات التعامل مع لينكس
-
يُعَد فهم الشبكات جزءًا أساسيًّا من تهيئة كيانات معقدة على الإنترنت، ويؤثر ذلك كثيرًا على كفاءة تواصل الخوادم بين بعضها بعضًا، وتطوير ضوابط أمان للشبكات، وإبقاء عقد الاتّصال منظمة. شرحنا في الدليل السابق بعض المصطلحات المهمة في عالم الشبكات. اطّلع على الدليل إذا أردت فهم المفاهيم، التي سنستعرضها هنا. سنناقش في هذا المقال بعضًا من المفاهيم المرتبطة بتصميم شبكات الحاسوب والتفاعل معها، كما سنغطّي على وجه التحديد أصناف الشبكات Network Classes، والشبكات الفرعية Subnets، والتوجيه غير الصنفي بين النطاقات CIDR لتجميع عناوين بروتوكول الإنترنت. فهم عناوين بروتوكول الإنترنت IP Addresses يجب أن يحتوي كلّ موقعٍ أو جهاز على الشبكة عنوانًا يدل عليه، وهذا يعني أنه يمكن الوصول إلى أي موقع أو جهاز عن طريق معرفة العنوان الخاص به، بحيث ينتمي هذا العنوان لنظام عناوينٍ ما مُعرَّف بواسطته؛ ففي حالة شبكة من نمط بروتوكول الإنترنت TCP/IP الاعتياديّة، يُعالَج الأمر باستخدام عدة طبقات، لكن عندما نذكر "عنوانًا على الشبكة" فنحن نقصد غالبًا عنوان بروتوكول الإنترنت. تمكننا عناوين بروتوكول الإنترنت من الوصول لموارد شبكةٍ ما باستخدام واجهتها Interface؛ فإذا أردنا إجراء اتّصال بين حاسوبٍ وحاسوب آخر مثلًا، فنحن بحاجة لإرسال المعلومات باستخدام بروتوكول الإنترنت الخاص بالحاسوب المتلقّي. يمكننا نظريًا فعل ذلك، وهذا بفرض أنّ الجهازين على الشبكة نفسها، أو أن هناك بعض الأجهزة الوسيطة التي تفسّر الطلب عبر عدّة شبكات. لا ينبغي تكرار عنوان بروتوكول الإنترنت ذاته على الشبكة نفسها، فكلّ عنوان فريدٌ عن غيره؛ ويمكن للشبكات أن تُعزل أو تُربط ببعضها بعضًا بعد تهيئتها لتضمن الوصول بين شبكتين منفصلتين. يسمح نظام ترجمة عنوان الشبكة Network Address Translation -أو اختصارًا NAT-، بإعادة كتابة العناوين عندَ إرسال حزمٍ من البيانات بين شبكتين، وذلك بهدف وصول الحزم لوجهتها النهائية. وبالتالي يمكننا بفضل هذا النظام استعمال عناوين متشابهة ضمن عدّة شبكاتٍ معزولة دون أيّ مشكلات. الفرق بين معياري IPv4 وIPv6 هناك معياران لبروتوكول الإنترنت، ومُستخدمان بكثرة ضمن أنظمة الاتّصالات، هما: معيار IPv4 ومعيار IPv6. نظرًا للتحسينات التي طرأت على معيار IPv6، فهو يستبدل ببطء معيار IPv4 المحدود؛ والسبب في ذلك ببساطة هو احتواء العالم على كثيرٍ من الأجهزة المتصلة بالإنترنت ولا يكفي معيار IPv4 لسدّ الحاجة. يكون عنوان معيار IPv4 بطول 32 بِت، ويُقسَّم إلى 4 أقسام، إذ يكون كل واحدٍ منها بطول بايت أو 8 بِت، ويفصل بين كل بايتٍ وآخر نقطة ويُمثَّل كل بايت برقم من 0 إلى 255. بالرغم من أنّ العنوان يتألّف من أرقام في النظام العشري ليسهل التعامل بها بواسطتنا نحن البشر، إلّا أنّها في الحقيقة قيمٌ مُمثّلة بثمان خانات (واحدات وأصفار)، وتدعى كل 8 خانات بثُمانيّة Octet. يبدو عنوان IPv4 الاعتيادي على النحو التالي: 192.168.0.5 إذ أن أعلى قيمة ممكنة هي 255، وأصغر قيمة هي 0. يمكننا التعبير عن العنوان السابق باستخدام النظام الثنائي، بحيث نفصل بين كل ثمانيّة وأخرى بشَرطة بدل نقطة، ونفصل بمسافةٍ بين كل 4 خانات بهدف تسهيل قراءة العنوان: 1010 0000 - 0000 0000 - 1000 1010 - 0000 1100 لا بُد أن نفهم جيّدًا أن التمثيلين السابقين متكافئين، فهذا مهمّ لاستيعاب المفاهيم التي سترِد لاحقًا. على الرغم من وجود بعض الاختلافات الأخرى بين المعيارين بخصوص البروتوكول وكيف يعمل كلّ منهما، إلّا أن الفرق الأبرز هو اختلاف سعة كلّ منهما؛ إذ يُمثَّل معيار IPv6 عنوان بروتوكول الإنترنت باستخدام رقم مؤلف من 128 بِت؛ وبالتالي يحوي IPv6 ما يساوي: 7.9 x 10<sup>28 </sup> ضعف ما يستطيع معيار IPv4 تمثيله. يُكتب عنوان بروتوكول الإنترنت بمعيار IPv6 متمثّلًا بثمانية أقسام؛ ويتكوّن كل قسم من 4 خانات في النظام السداسي عشري. يحتوي النظام السداسي عشري على 16 رقم من 0 إلى 15، ويُمثَّل بالأرقام من 0 إلى 9 بالإضافة للأحرف من A إلى F. هذا ما قد يبدو عليه عنوان بروتوكول إنترنت بمعيار IPv6: 1203:8fe0:fe80:b897:8990:8a7c:99bf:323d قد ترى أن بعض العناوين مكتوبةً أقصر من السابق، إذ يمكن اختصار الخانات الصفرية اليسرى لأي ثمانيّة دون أن تؤثر على قيمة العنوان، كما يمكن اختصار ثمانيّة صفرية باستخدام "::". على سبيل المثال إن كان لدينا ثمانيّة ما في عنوان IPv6 على النحو التالي: ...:00bc:... فيمكننا اختصاره إلى: ...:bc:... لتوضيح الحالة الثانية المذكورة، سنفترض أنه لدينا العنوان التالي الذي يحتوي على ثمانيّات صفريّة: ...:18bc:0000:0000:0000:00ff:... يمكن كتابته بصيغةٍ مختصرة، وذلك عن طريق إزالة الأصفار اليسرى واستبدال الثمانيّات الصفرية بالرمز "::"، وفق: ...:18bc::ff... ولكن يمكن اختصار الثمانيّات الصفرية مرّةً واحدةً في العنوان؛ وإلّا فلن تكون قادرًا على إعادته للعنوان الكامل مرةً أخرى. رغم أنّ المعيار IPv6 أكثر شيوعًا وانتشارًا هذه الأيام، إلّا أننا سنبني شرحنا وأمثلتنا على معيار IPv4، نظرًا لسهولة التعامل معه واحتوائه على خاناتٍ أقلّ. أصناف عنوان IPv4 والنطاقات المحجوزة تحتوي عناوين بروتوكول الإنترنت عادةً على مكوّنين يدلّان على معلومتين؛ إذ يدلّ المكون الأول على الشبكة network التي ينتمي العنوان إليها؛ بينما يدلّ المكون الثاني على جهاز معيّن (مضيف host) على الشبكة. ويعتمد موضع بدء المكون وانتهائه على الطريقة التي هُيئت بها الشبكة، وسنتكلّم عن ذلك في الفقرات التالية. تتوزع عناوين IPv4 على أربعة أقسام تدعى بالأصناف Classes، من A إلى E، هدفها الفصل بين نطاقات عناوين الإنترنت المتاحة وتجزئتها. يمكننا معرفة صنف كل عنوان عن طريق ملاحظة أول أربعة بِتات، إذ يُعرَّف صنف العنوان بواسطتها. نستطيع التعرف على صنف العنوان على النحو التالي: الصنف A 0---: يبدأ أول بِت من عنوان IPv4 ذو الصنف A بالصفر، وأي عنوان يقع بين المجال 0.0.0.0 و127.255.255.255 ينتمي للصنف A. الصنف B 10--: يحتوي الصنف B على جميع العناوين بين المجال 128.0.0.0 و 191.255.255.255. أو بمعنى آخر، أي عنوانٍ يحتوي على 1 في البِت الأول، و 0 في البِت الثاني. الصنف C 110-: يحتوي الصنف C على جميع العناوين بين المجال 192.0.0.0 و223.255.255.255. أو بمعنى آخر، أي عنوانٍ يحتوي على 1 في البِت الأول والثاني، و0 في الثالث. الصنف D 1110: يحتوي الصنف D على جميع العناوين بين المجال 244.0.0.0 و329.255.255.255. بمعنى آخر، أي عنوانٍ يحتوي على 1 في البِت الأول والثاني والثالث، و0 في الرابع. الصنف E 1111: يحتوي الصنف E على جميع العناوين بين المجال 240.0.0.0 و255.255.255.255. بمعنى آخر، أي عنوان يحتوي على 1 في البِت الأول والثاني والثالث والرابع. الصنف D مخصّص لبروتوكولات التحويل المتعدد Multi-casting، التي تسمح بإرسال حزم البيانات لمجموعةٍ من المضيفين دفعةً واحدةً. عناوين الصنف E محجوزة للاستخدام المستقبلي والتجريبي، وهي غير مُستخدمة غالبًا. تقسِّم كل من الأصناف A و B و C الشبكات والأجهزة المضيفة على نحوٍ مختلف وفقًا لحجم الشبكة؛ إذ يستخدم الصنف A الخانات المتبقية من الثُمانيّة الأولى في تمثيل الشبكة، ويُمثّل المضيف بباقي العنوان. يُعد هذا الصنف جيدًا لتعريف شبكاتٍ قليلة تضمّ كثيرًا من الأجهزة المضيفة. يستخدم الصنف B أوّل ثمانيّتان (المتبقي من الثمانية الأولى، وكامل الثمانية الثانية) في تمثيل الشبكة والمتبقي من العنوان لتمثيل الأجهزة المضيفة؛ بينما يستخدم الصنف C أوّل ثلاث ثمانيّات لتمثيل الشبكة، والثمانية الأخيرة لتمثيل الجهاز المضيف داخل الشبكة. تقسيم عنوان بروتوكول الإنترنت لأصناف عدّة طريقةٌ قديمةٌ بعض الشيء، إذ قُدِّمَت هذه الطريقة مثل حلٍ لمشكلة نفاذ عناوين IPv4، وذلك نظرًا لإمكانية وجود عدة حواسيب للمضيف ذاته وتابعة لشبكاتٍ مختلفة، واستُبدلت هذه الطريقة بطرقٍ أخرى سنناقشها. النطاقات الخاصة المحجوزة هناك بعض الأجزاء من عنوان IPv4 المحجوزة لأغراضٍ معينة. ويُعد نطاق الاسترجاع Loopback Range واحدًا من أكثر النطاقات المحجوزة إفادةً، وهو مُحددٌ بالنطاقات ما بين 127.0.0.0 و 127.255.255.255؛ إذ يُستخدم هذا النطاق من قبل المضيف لفحص سلامة الشبكة، ويُستخدم أوّل عنوان في النطاق غالبًا وهو 127.0.0.1. يمتلك كل صنف من أصناف عنوان الإنترنت نطاقًا محجوزًا للشبكات الخاصة؛ إذ أن نطاق العناوين من 10.0.0.0 إلى 10.255.255.255 محجوزٌ للشبكات الخاصة للعناوين من الصنف A؛ بينما يوجد للصنف B النطاق المحجوز من 172.16.0.0 إلى 172.31.255.255، وأخيرًا للصنف C النطاق من 192.168.0.0 إلى 192.168.255.255. يمكن لأي حاسوب غير متّصل بالإنترنت مباشرةً، بمعنى غير متصل عبر جهاز توجيه Router، أو نظامٍ لترجمة عناوين الشبكات NAT، استخدام العناوين المذكورة في أي وقت. هناك بعض نطاقات العناوين الإضافية المحجوزة لأغراض معيّنة، يمكنك مراجعة ملخص ويكيبيديا لمزيدٍ من المعلومات عنها. أقنعة الشبكة والشبكات الفرعية تُدعى عملية تقسيم الشبكة إلى شبكات فرعية أصغر منها بتفريع الشبكة Subnetting، إذ تُعد هذه العملية مفيدةً في كثيرٍ من الحالات، وتساعد بعزل مجموعات من المضيفين عن بعضها بعضًا للتعامل معها بسهولة. كما ناقشنا سابقًا، يمكن تجزئة كل نطاق من عناوين بروتوكول الإنترنت إلى جزء يدل على الشبكة، وآخر يدل على المضيف؛ إذ يعود طول الجزء الذي يدلّ على كلٍّ منهما للصنف الذي ينتمي إليه هذا العنوان؛ فبالنسبة لعنوانٍ من الصنف C مثلًا، تُستخدم أوّل ثلاث ثمانيّات للدلالة على الشبكة. إذًا، يدل الجزء 192.168.0 بالنسبة للعنوان 192.168.0.15 على الشبكة، ويدلّ 15 على المضيف. تحتوي كل شبكة افتراضيًا على شبكةٍ فرعية واحدة، وتضمّ هذه الشبكة الفرعية على جميع عناوين المضيفين المعرّفة بداخلها. ويُعَد قناع الشبكة بمثابة توصيفٍ لعدد بِتّات العنوان المستخدم في الشبكة الفرعية هذه؛ وقناع الشبكة الفرعية هو قناع شبكة آخر يُستخدم لتقسيم الشبكة لأقسامٍ أكثر. كلّ بِت في العنوان هامٌ في التمثيل والدلالة على الشبكة يجب أن يُمثّل بـ 1 في قناع الشبكة، إذ يمكننا مثلًا تمثيل العنوان 192.168.0.15، الذي ناقشناه في مثالنا السابق بالترميز الثنائي على النحو التالي: 1100 0000 - 1010 1000 - 0000 0000 - 0000 1111 كما شرحنا في السابق، تمثّل الثمانيّات الثلاثة الأولى الجزء الذي يدل على الشبكة في عنوان من التصنيف C، أو بكلماتٍ أخرى، الأربع وعشرين بتًا الأوّليات. ونظرًا لأن هذه هي البتات المهمة التي نريد الاحتفاظ بها، فسيكون قناع الشبكة على النحو التالي: 0000 0000 - 1111 1111 - 1111 1111 - 1111 1111 يمكننا كتابة قناع الشبكة السابق بترميز IPv4 اعتيادي، أي 255.255.255.0؛ إذ أن كل بِت يتخذ القيمة 0 بالتمثيل الثنائي لقناع الشبكة يدلّ على أنّه جزءٌ من العنوان الذي يمثل المضيف، ويمكن عدّه متغيرًا. تُعَد البِتّات التي تتخذ القيمة 1 ثابتة القيمة ضمن الشبكة أو الشبكة الفرعية. يمكننا الحصول على الجزء الدالّ على الشبكة بإجراء عمليّة ثنائية من نوع AND بين عنوان بروتوكول الإنترنت وقناع الشبكة. وبإجراء هذه العملية، نحافظ على القسم الذي يدل على الشبكة في العنوان ونهمل القسم الذي يدل على المضيف. ستكون نتيجة العملية وفقًا لمثالنا السابق على النحو التالي: 0000 0000 - 0000 0000 - 0000 1010 - 0000 1100 يمكننا تمثيل العنوان السابق على النحو التالي: 192.168.0.0. النطاق الذي يدل على المضيف هو الفرق بين القيم الأصلية وقسم المضيف. وفي هذه الحالة، قيمة نطاق المضيف، هي: 1111 0000 أو 15. الهدف من تفريع الشبكة هو استخدام قسمٍ من نطاق المضيف في العنوان، واستخدامه لتقسيم العنوان من جديد. إذ يزودنا قناع الشبكة للعنوان 255.255.255.0 مثلًا، بـ 254 مضيف على الشبكة (الرقمان 0 و255 محجوزان). إذا أردنا تقسيم الشبكة لشبكتين فرعيتين، يمكننا استخدام بِت واحد من قسم العنوان الذي يدل على المضيف قناعًا للشبكة. بعودتنا للمثال السابق، القسم الذي يعود للشبكة هو: 0000 0000 - 1000 1010- 0000 1100 والقسم الذي يعود للمضيف هو: 1111 0000 يمكننا استخدام أول بِت من القسم الذي يدلّ على المضيف وتغييره لإنشاء شبكة فرعية، وذلك بتعديل قناع الشبكة من هذا القناع: 0000 0000 - 1111 1111 - 1111 1111 - 1111 1111 لهذا القناع: 0000 1000 - 1111 1111 - 1111 1111 - 1111 1111 في ترميز IPv4 الاعتيادي، يُمثّل العنوان السابق على النحو التالي: 192.168.0.128. خصّصنا بهذه العملية أوّل بِت في الثمانيّة الأخيرة على أنها خانة ذات أهمية تدل على الشبكة، إذ تعطينا هذه العملية شبكتين فرعيتين؛ ويغطي قناع الشبكة الأول النطاق من 192.168.0.1 إلى 192.168.0.127؛ بينما يغطي قناع الشبكة الثاني النطاق 192.168.0.129 إلى 192.168.0.255. بطبيعة الحال، يجب ألاّ تُستخدم الشبكة الفرعية بنفسها عنوانًا. إذا استخدمنا بِتّات أكثر من قسم العنوان العائد للشبكة، عندها يمكننا الحصول على شبكاتٍ فرعية أكثر وأكثر. ألقِ نظرةً على بناء مخطط لعناوين IP عبر الشبكات الفرعية لمزيدٍ من المعلومات. ترميز التوجيه غير الصنفي بين النطاقات CIDR طُوِّر نظام التوجيه غير الصنفي بين النطاقات Classless Inter-Domain Routing -أو اختصارًا CIDR- بديلًا لطريقة تفريع الشبكة التقليدي، إذ يعتمد مبدأ هذا النظام على إضافة خانةٍ جديدة لعنوان بروتوكول الإنترنت بنفسه للدلالة على عدد البِتّات التي تمثل القسم الخاص بتوجيه الشبكة. على سبيل المثال، عنوان بروتوكول الإنترنت 192.168.0.15 مرتبطٌ بقناع الشبكة 255.255.255.0، ويمكننا التعبير عن ذلك بترميز CIDR عن طريق كتابة 192.168.0.15/24؛ إذ يمثل العدد 24 عدد البِتّات الموجودة في عنوان بروتوكول الإنترنت، والتي تدل على القسم الخاص بتوجيه الشبكة. يفتح هذا النظام المجال لفرصٍ مثيرةٍ للاهتمام، إذ يمكن استخدامه للدلالة على الشبكات الفوقية Supernets، ونقصد هنا نطاقات عناوين كبيرة من غير الممكن تمثيلها بطريقة قناع الشبكة الاعتياديّة. على سبيل المثال، في شبكةٍ من الصنف C، مثل الشبكة في أمثلتنا السابقة، لا يمكننا جمع العنوانين 192.168.0.0 و192.168.1.0، وذلك نظرًا لأن عنوان قناع الشبكة للصنف C هو 255.255.255.0، ولكن مع استخدام ترميز CIDR، يمكننا جمع العنوانين بالدلالة على النطاق الذي ينتميان إليه على النحو التالي 192.168.0.0/23؛ وهذا يدلّ على أن هناك 23 بِت مُستخدمٌ للدلالة على الشبكة التي نقصدها. يمكننا تمثيل الشبكة الأولى 192.168.0.0 بالترميز الثنائي على النحو التالي: 0000 0000 - 0000 0000 - 1000 1010 - 0000 1100 ونمثل الشبكة الثانية 192.168.1.0 على النحو التالي: 0000 0000 - 0001 0000 - 1000 1010 - 0000 1100 وبالتالي، يدلنا عنوان CIDR الذي كتبناه سابقًا، على أوّل 24 بِت مستخدَم لتمثيل القسم الذي يعود للشبكة، ويساوي هذا التمثيل قناع الشبكة 255.255.254.0، أو بالتمثيل الثنائي على النحو التالي: 0000 0000 - 1110 1111 - 1111 1111 - 1111 1111 كما نلاحظ، يمكن أن يكون البِت ذو الترتيب 24 بقيمة 1 أو 0، لأن الجزء الذي يدل على الشبكة يشمل فقط أوّل 23 بِت. يمنح نظام CIDR مزيدًا من التحكم للدلالة على نطاقاتٍ مستمرّة من عنوان بروتوكول الإنترنت، وهو أفضل من طريقة قناع الشبكة التي استعرضناها سابقًا. الخاتمة نأمل أنك استطعت فهم بعض تطبيقات الشبكات على عناوين بروتوكول الإنترنت. مع أنّ صعوبة التعامل مع هذا النوع من الشبكات كبيرة وغير بديهية، إلا أنها هامة لفهم طريقة تهيئة مكونات شبكتك وبرمجياتها. هناك الكثير من الأدوات والآلات الحاسبة على الإنترنت التي ستساعدك في فهم بعض من هذه المبادئ، والحصول على نطاقات عناوين صحيحة من خلال إدخال بعض المعلومات. توفر بعض الأدوات تحويلًا لعنوان بروتوكول الإنترنت من التمثيل العشري الاعتيادي إلى التمثيل الثنائي (الثمانيّات)، وتعرض لك تمثيلًا بصريًّا لأقنعة شبكة نظام CIDR. ترجمة -وبتصرف- للمقال Understanding IP Addresses, Subnets, and CIDR Notation for Networking لصاحبه Justing Ellingwood. اقرأ أيضًا الشبكات الفرعية والعناوين والأخطاء في بروتوكول IP بناء مخطط لعناوين IP عبر الشبكات الفرعية عند بناء الشبكات التوجيه بين الشبكات المحلية الافتراضية VLANs استكشاف وظائف الموجهات في الشبكات معمارية الشبكة الحاسوبية وشبكة الإنترنت (Network Architecture)
-
يميل الكثير من الناس لإهمال جانبٍ معيّن من عملية تطوير البرمجيات، ألا وهو كيفية ترخيص البرنامج، حيث تُملي رخصة البرنامج كيفية استعمال وتوزيع الشيفرة المصدرية بواسطة المُرخّص لهم -أي المستخدمين-، وهذا من شأنه أن يؤثر بشدة على حجم تبنّي الحلّ التقني. تُباع معظم البرامج باستخدام رخصة احتكارية تسمح للناشر أو المطوّر بالاحتفاظ بكامل الحقوق الفكرية للبرنامج. هناك بعض وجهات النظر التي تنظر لهذه الرخصة بأنها تضيف قدرة تحكم إضافية للناشر يمكن الاستغناء عنها، إذ يساهم منع الأشخاص المُرخّص لهم بنسخ وتغيير الشيفرة المصدرية للبرنامج بكبح ناشري البرامج الاحتكاريّة من الابتكار والتطوير المحتمل لتقنيات جديدة. ألهم هذا الموقف في إيجاد رخَص تعطي الحقّ للمستخدمين برؤية الشيفرة المصدرية وتعديلها ومشاركتها حسب رغبتهم، وعُرفت البرمجيات المرخّصة وفقًا لتلك الطريقة بأحد الإسمين: البرمجيات الحرّة؛ أو بالبرمجيات مفتوحة المصدر. يرمز كلا المصطلحين للمعنى ذاته عمومًا، وهو: برمجيات تحتوي على القليل من القيود المتعلّقة بكيفية استعمالها، ويرى مناصروها أن البرمجيات الحرّة والبرمجيات مفتوحة المصدر أكثر أمانًا وفاعليّةً، وأنها تعمل بموثوقية أكبر من البرمجيات الاحتكارية. لمَ نستخدم إذًا تسميتين مختلفتين للدلالة على الشيء ذاته؟ يتضمّن التفسير فهمًا لتاريخ كلا المصطلحين، وفهمًا أعمق للتفاصيل الدقيقة التي تفصل المصطلحين المختلفين. لمحة تاريخية لا تُعد إمكانية السماح للفرد برؤية وتعديل ومشاركة الشيفرة المصدرية لبرمجيّة معيّنة دون أي ملاحقة قانونيّة فكرةً جديدة؛ فقد كانت توزّع البرمجيات قبل سبعينيات القرن الماضي مصحوبةً بشيفرتها المصدرية، والسبب في ذلك هو أنّ هذه البرمجيات كانت مخصّصةً لمتطلبات العتاد الصلب، وعلى المستخدم أن يعدلها قبل أن تعمل بنجاح على حاسوبه، أو لإضافة وظائف جديدة. كان التعامل مع الحواسيب في هذه الفترة لمعظم الأشخاص مقتصرًا على أهدافٍ أكاديميّة أو بحثيّة. ومعنى هذا، أن موارد الحوسبة كانت تُشارَك أغلب الأحيان، وكان تغيير البرمجيات لخلق طرق أكثر فاعليّةً ووثوقيّةً لسير العمل مرحّبًا به للغاية. طوّر مشروع المارد Genie في جامعة كاليفورنيا بيركلي مثلًا، نظام تشغيل بيركلي لمشاركة الوقت؛ وهو نظام تشغيل بُنيَ من الصفر عن طريق التعديل على الشيفرة المصدرية لحاسوب SDS 930. مع ارتفاع تكلفة تطوير البرمجيّات وزيادة تعقيدها، توصلت الشركات لطرق أوقفت المشاركة لشيفرتها المصدرية دون أيّ رقيب، وذلك لحماية مصدر ربحهم ومنع المنافسين من الاطّلاع على حلولهم التقنية. عندها، بدأت عملية وضع القيود القانونيّة، متضمّنةً حقوق التأليف والنشر وعقود الشراء، وباعت هذه الشركات منتجاتها تحت مظلّة الرُخَص الاحتكاريّة. ومع نهايات سبعينيّات القرن الماضي، توقفت معظم الشركات المنتجة للبرمجيات عن تضمين الشيفرة المصدرية لمنتجاتها، وأدّت هذه الحركة لاستياء العديد من مستخدمي الحواسيب القدامى، وكانت نتيجتها إيجاد حركة البرمجيات الحرّة. بدايات البرمجيات الحرة كانت حركة البرمجيات الحرة Free Software Movement بالمعظم وليدة أفكار شخصٍ واحد هو ريتشارد ستالمان؛ حيث بدأ ستالمان دراساته في علوم الحاسوب بدايات السبعينيّات، وذلك قبل ولادة البرامج الاحتكاريّة بفترة قصيرة، وعملَ باحثًا في مختبرات جامعة ماساتشوستس للذكاء الاصطناعي في بدايات الثمانينيّات. كان ستالمان يُكِنّ كثيرًا من الإحباط اتجاه انتشار البرمجيات الاحتكارية بصفته عضوًا في مجتمع القرصنة الأكاديمي لما يقارب القرن، ورأى أن انتشارها ينتهك حق الناس في ابتكار وتحسين برمجياتٍ موجودة. أطلق ستالمان في عام 1983 مشروعًا سمّاه باسم مشروع جنو GNU في سبيل إنشاء نظام تشغيل متكامل يسمح لمستخدميه برؤية وتعديل ومشاركة الشيفرة المصدريّة. حدّد ستالمان أهدافه من هذا المشروع في بيان جنو، والذي ذكر فيه بأنّ الرخص الاحتكارية تعيق تطوير البرمجيّات التي يقودها المجتمع، وهذا يؤدي بدوره إلى ركود الابتكار ويعيق تقدُّم التقنية. يضيف هذا الأمر وفقًا لستالمان، عبئًا غير عادل على المستخدمين والمطوّرين الذين أمكنهم عوضًا عن ذلك تعديل الشيفرة البرمجيّة لتلائم حاجاتهم، أو تغييرها لتُستخدَم في غرضٍ جديد. لقد نُظِرَ إلى مشروع جنو على أنه استجابةً للبرمجيات الاحتكارية وتذكيرًا للناس بالعصر الذي سبقها، وذلك عندما كانت الشيفرة المصدريّة للبرمجيات تُشارَك بحريّة بهدف تطوير البرمجيات التعاوني. بنى ستالمان في عام 1985 مؤسسة البرمجيات الحرّة، أو كما تُعرف باسم Free Softwar ،Foundation -أو اختصارًا FSS-، معتمدًا على مشروع جنو حجرًا للأساس، وهي مؤسسةٌ غير ربحية هدفها نشر ثقافة البرمجيات الحرة للعامّة. كما طور ستالمان فيما بعد رخصة جنو العموميّة GPL؛ وهي رخصة برمجيات تمنح الحق للمستخدم بتشغيل الشيفرة المصدرية ورؤيتها ومشاركتها بحرية مطلقة. اعتمادًا على تعريف مؤسسة البرمجيات الحرة، يجب أن تتضمَن أي برمجيّة الحريات الأربع التالية لمستخدميها لتُصنّف برمجيّةً حرة: حرية تشغيل البرنامج متى أراد المستخدم، ولأي غرض. حرية معرفة طريقة عمل البرنامج، وتغييرها بناءً على رغبة المستخدم، فالوصول للشيفرة المصدرية هو شرطٌ مسبق لهذه الحرية. حرية توزيع نسخةٍ من الشيفرة المصدريّة لمعارفك. حرية توزيع نسخ من برنامجك المعدّل للآخرين؛ إذ يمكنك بفعل هذا إتاحة الفرصة لكامل المجتمع بالاستفادة من تعديلاتك. ويُعَد الوصول للشيفرة المصدرية شرطًا مسبقًا لهذه الحرية أيضًا. تعد مؤسسة البرمجيات الحرّة أي برمجيةٍ تخفق بتحقيق هذه الحريات الأربع برمجيّةً غير حرّة، وبذلك غير أخلاقيّة. ولادة البرمجيات مفتوحة المصدر اختار ستالمان مصطلح "البرمجيات الحرّة" للدلالة على المبدأ الذي ينصّ على حرية تعديل الشيفرة المصدرية، ومشاركتها لأي برنامجٍ من مستخدميه وفق رغبتهم. خلقت هذه التسمية كثيرًا من الخلط لدى الناس على مرور السنوات، لاعتقادهم بأنّ البرمجيات الحرة هي البرامج التي يمكنهم الحصول عليها مجانًا دون دفع أي مقابل مادّي؛ وذلك نظرًا لازدواجية معنى كلمة Free، فهي تعني حرّ ومجاني في الوقت ذاته حسب السياق. توضّح مؤسسة البرمجيات الحرة هذا الخلط بالمعاني بواسطة مقولتها "فكّر بالكلمة بسياق حرية الرأي، وليس بسياق مشروبٍ مجاني". تنامى القلق بين مناصري مشروع جنو ونظام لينكس في نهايات تسعينيات القرن الماضي، فقد كان من الممكن أن يتسبب هذا الخلط في المعنى بسوء فهم الناس لمصطلح البرمجيات الحرة، وإيجابياتها موازنةً مع البرمجيات الاحتكارية. كما كانت مؤسسة البرمجيات الحرّة شديدة اللهجة ولاذعة الانتقاد ضدّ جميع أشكال البرمجيات الاحتكارية، وهذا سبّب قلقًا من طرف مناصري البرمجيات الحرّة بكونه سلوكًا عدائيًّا تجاه المصالح التجارية، الذي سيتسبّب في النهاية بعرقلة انتشار حركة البرمجيات الحرة. الكاتدرائية والبازار كتب إريك ريموند في عام 1997 -وهو مطوّرٌ ومناصرٌ للبرمجيات الحرة-، مقالًا بعنوان الكاتدرائية والبازار The Cathedral and the Bazaar؛ وهي مقالةٌ شهيرة توازن بين نمطين من أنماط تطوير البرمجيات المُستخدمة في تطوير مختلف مشاريع البرمجيات الحرّة. ترمز الكاتدرائية هنا للنمط الهرمي في تطوير البرمجيات، إذ تطور مجموعةٌ معينةٌ من المطورين البرمجيات، مثل عملية تطوير محرّر النصوص إيماكس Emacs على نظام جنو؛ بينما يرمز البازار لنمط التطوير المتمثّل بنشر الشيفرة المصدرية للعموم وتطويرها علنيًا، مثل عملية تطوير نواة لينكس. تتمحور فكرة المقال حول كفاءة نمط البازار في إيجاد الأخطاء البرمجية وحلّها موازنةً مع النمط الآخر، وذلك بحكم أن هناك عددٌ أكبر من الأشخاص القادرين على مراجعة وتجربة الشيفرة المصدرية للبرنامج. لذا، يزعم ريموند أن تبني هذا النمط المعتمد على الجهد المجتمعي ينتج بالحصول على برمجياتٍ أكثر أمانًا وموثوقيّة. عُقب الأفكار التي طرحها ريموند في مقالته الكاتدرائية والبازار، أطلقت شركة نتسكيب Netscape الشيفرة المصدريّة لمتصفح الويب الخاص بها المسمى بالمتواصل Communicator مجانًا ومفتوح المصدر للعموم في عام 1998؛ وسيشكّل هذا المتصفح أساس متصفح موزيلا فيرفكس 1.0 لاحقًا. فكّرت مجموعةٌ من مناصري البرمجيات الحرّة، متضمنةً ريموند ولينوس تورفالدس وفيل زيمرمان وآخرون، بإعادة تسويق حركة البرمجيات الحرة، وصرف تركيزها بعيدًا عن الدوافع الفلسفيّة والأخلاقية، مستلهمين ذلك من تجرِبة Netscape والإمكانية التجارية التي أتيحت لها بعد هذه العملية. اختارت هذه المجموعة تسمية "مفتوح المصدر" للدلالة على البرمجيات المشاركة مجانًا، آملين أن تظهر هذه التسمية الفائدة المضافة من نمط تطوير البرمجيات الاجتماعي والتعاوني. بعد ذلك بوقت وجيز، أوجد كلٌّ من ريموند وبروس بيرنز مبادرة المصدر المفتوح Open Source Initiative -أو اختصارًا OSI-، لتشجيع استخدام التسمية الجديدة ونشر مبادئ البرمجيات مفتوحة المصدر، إذ طوّرت هذه المبادرة تعريف المصدر المفتوح؛ وهي لائحةٌ من عشرة مبادئ تصف معايير رخصة أي برمجية لتعدّ مفتوحة المصدر، وهي: حرية إعادة التوزيع: يجب ألّا تمنع الرخصة أي طرفٍ من بيع أو منح البرمجيّة، مثل إضافةٍ لبرمجية أكبر منها تحتوي برمجياتٍ من مصادرٍ متعددة. الشيفرة المصدرية: يجب أن يحتوي البرنامج على الشيفرة المصدرية، وأن يسمح بتوزيع البرنامج مثل شيفرة مصدريّة أو إصدار مُجمّع. الأعمال المشتقة: يجب أن تسمح الرخصة بالتعديل وإنشاء برمجيات مشتقة، وأن تسمح بتوزيعها تحت شروط رخصة البرنامج الأصلي ذاتها. الحفاظ على الشيفرة المصدرية للمؤلّف: يجوز للرخصة أن تمنع توزيع البرامج المعدّلة المبنية على الشيفرة المصدرية الأصل، وذلك في حالة سماح الرخصة بتوزيع ملفات الترقيع patch files مع الشيفرة المصدرية الأصل بهدف تعديل البرنامج بعد تجميعه Compiling. يُمنَع التمييز ضدّ شخص ما أو مجموعة من الأشخاص: على الرخصة ألّا تميّز بين الأشخاص أو مجموعة من الأشخاص. يُمنَع التمييز ضد مجال أو مهنة معيّنة: يجب ألا تمنع الرخصة أي أحدٍ من استخدام البرنامج بحرية بهدف توظيفه في أي مجال أو غرض. توزيع الرخصة: يجب أن تُطبّق الحقوق الموجودة على رخصة البرنامج الأصلية على جميع البرامج المشتقة دون الحاجة لتضمين رخصة إضافية من الموزّعين. لا ينبغي للرخصة أن تكون مخصصة لمنتج معين: لا يشترط على الحقوق المُضمّنة في برنامجٍ ما أن تكون فعالةً بحالة كان جزءًا من برنامج معين فقط. لا ينبغي على الرخصة أن تضع قيودًا على برمجياتٍ أخرى: يجب ألّا تضع الرخصة قيودًا على برنامج معيّن مُوزعٍ مع البرنامج المرخّص. على الرخصة أن تكون محايدة: لا يجوز أن تستند الرخصة على أي تقنيةٍ فردية أو نمط واجهة مستخدم. الاختلافات بين البرمجيات الحرة ومفتوحة المصدر يُعد الفرق بين مصطلح "البرمجيات الحرة" و"البرمجيات مفتوحة المصدر" ضئيلًا في العموم، ويأتي الاختلاف بالتسمية من فروقات فلسفيّة. تقول مبادرة المصدر المفتوح، أن كلا المصطلحين يعنيان الأمر ذاته، ويمكن استخدامهما بالتبادل في أي سياق كان. يفضّل مصطلح "مفتوح المصدر" ببساطة، لأنّه يُعتقد أنه المصطلح الأفضل الذي يظهر طبيعة البرمجيات ونوايا مؤلّفيها في كيفية استخدامها. أما بالنسبة لمناصري فلسفة "البرمجيات الحرة"، فلا يصف المصطلح "مفتوح المصدر" بالضبط أهميّة الحركة، والمشاكل الاجتماعية التي قد تحصل بسبب البرامج الاحتكارية. ترى مؤسسة البرمجيات الحرة أنّ مبادرة المصدر المفتوح تولي كثيرًا من الاهتمام لتسويق ونشر الفوائد العملية من البرامج غير الاحتكارية، متضمّنًة ربحية العملية وكفاءة نمط التطوير التعاوني، وذلك على حساب الاهتمام للمشاكل الأخلاقية الناتجة عن تقييد حقوق المستخدمين في تغيير وتحسين الشيفرة المصدرية طبقًا لرغبتهم. يعتمد تصنيف البرمجية بكونها حرةً أو مفتوحة المصدر، على شروط رخصتها وتحقيقها لشروط كلٍّ من مبادرة المصدر المفتوح، ومؤسسة البرمجيات الحرة، أو كلاهما. هناك نقاط كثيرة مشتركة بين الاثنين، ولكن هناك بعض الاستثناءات في بعض الأحيان؛ إذ تتوافق مثلًا رخصة ناسا مفتوحة المصدر مع معايير مبادرة المصدر المفتوح؛ بينما تعدّها مؤسسة البرمجيات الحرّة رخصة تقييديّة. لكن عمومًا، هناك فرصة كبيرة بكون رخصة ما مفتوحة المصدر إذا عُدّت حرّة. أسماء بديلة ظهرت على مرّ السنوات كثيرٌ من التسميات الأخرى في سعيٍ منها لحسم الخلاف؛ منها "برمجية حرة ومفتوحة المصدر Free and Open-source Software" -أو اختصارًا FOSS-، وهي واحدةٌ من أكثر التسميات استخدامًا، وتُعد تسميةً وسيطةً بين الطرفين. اكتسبت التسمية "برمجية حرة Libre Software" (إذ تأتي كلمة Libre من كلمة حرية Liberty)، أنصارًا هي الأخرى وأصبحت الكلمة FLOSS رائجةً أيضًا. تجدر الإشارة هنا إلى أنّ البرمجيات الحرة ومفتوحة المصدر مختلفةٌ كليًّا عن البرمجيات التي تنتمي للملكية العامة Public Domain؛ إذ تستمد البرمجيات الحرة ومفتوحة المصدر حرياتها من ترخيصها؛ بينما تستمد برمجيات الملكية العامة بعضًا من هذه الحريات من خلال الوقوع خارج نظام الترخيص. الفارق الواضح هنا هو أنه من الواجب توزيع كلٍّ من البرمجيات الحرة ومفتوحة المصدر تحت رخصة FOSS؛ بينما لا تخضع برمجيات الملكية العامة للشرط ذاته. المشكلة المتعلقة ببرمجيات الملكية العامة، هي عدم اعتراف جميع الدول في العالم بمحتواها غير الخاضع لحقوق الطبع والنشر. يجعل هذا الأمر الاعتراف بوجود برمجيّة تحت الملكية العامة عالميًّا أمرًا مستحيلًا. ولهذا السبب، لا تشجّع أيّ من مبادرة المصدر المفتوح OSI ومؤسسة البرمجيات الحرة FSF، بإطلاق المطورين برمجياتهم تحت الملكية العامة. الخاتمة يُستخدم كلا المصطلحين "برمجيات حرة" و"برمجيات مفتوحة المصدر" في معظم السياقات للدلالة على الشيء ذاته. وينبع تفضيل استخدام مصطلحٍ دونًا عن الآخر من اختلافٍ فلسفي بسيط؛ ولكن قد يصبح الفرق مهمًا بالنسبة للمطورين الذين يطوّرون برامجًا ويطلقونها للعالم، أو للنشطاء الذين يسعون لتغيير طريقة تفاعل الناس مع التقنية. بالتالي من المهم جدًا النظر للسلبيات والإيجابيات لمختلف الرُخَص والمتضمّنة الرُخَص الاحتكاريّة قبل نشر أيّ برنامج، واختيار الرُخصة المُثلى التي تناسب احتياجات الناشر. إن كنتَ مهتمًّا بقراءة مزيدٍ من التفاصيل عن مختلف الرخص المناسبة لمشروعك القادم، تقدّم مؤسسة البرمجيات الحرة لائحةً من الرُخَص تبيّن تفاصيل كلّ من الرخص الحرّة وغير الحرّة. كما قد تكون صفحة الرخص والمعايير على موقع مبادرة المصدر المفتوح مفيدةً لك. ترجمة -وبتصرف- للمقال The Difference Between Free and Open-Source Software لصاحبه Mark Drake. اقرأ أيضًا ما المقصود بمصطلح مفتوح المصدر (open source)؟ تعلم البرمجة تعرف على أنواع التراخيص الحرة التي تسمح لك ببيع المواد بشكل تجاري
-
-
- 7
-
شكراً لك، جوابك كان مفصل وما أحتاجه بالضبط. تمنياتي لك بالتوفيق صديقي. عندي سؤال آخر متعلق بموضوع الـ name و الـ id. هل من الضروري أن أقوم بإعطاء كل وسم name و id أم فقط الوسوم التي سيتم استخدامها؟ (كبرمجة مثلى).
- 3 اجابة
-
- 1
-
مرحباً، أردت الاستفسار عن الفرق بين الـ Attributes التالية : (name,id,for"input) ما الغرض التي أقوم باستعمال كلّ منها؟ شكراً لكم !
- 3 اجابة
-
- 1