المؤشرات Pointers في لغة البرمجة C
Ahmed AbuelfatehAhmed Abuelfateh

المؤشرات Pointers في لغة البرمجة C

شرح المؤشرات Pointers في لغة البرمجة C، ماهو تعريف المؤشر Pointer، كيفية إنشاء أو الإعلان عن المؤشرات في لغة السي، نبذة عن مكونات الذاكرة، كيفية تحديد عنوان المتغير في الذاكرة وعملية الـ Deferencing، أمثلة على المؤشرات في C

تعريف المؤشر Pointer

المؤشر أو الـ Pointer في لغة البرمجة C، هو عبارة عن متغير تحتوي قيمته على عنوان متغير آخر في الذاكرة، ومن الإسم فهو يشير إلى عنوان متغير آخر.

حجم المتغير من النوع Pointer يكون ثابت أي كان حجم أو نوع البيانات التي يقوم بالإشارة إليها، ويتم تحديد الحجم الخاص بالمؤشر طبقا لمعمارية وحدة المعالجة المركزية وبالتبعية نظام التشغيل المستهدف، عادة ما يتم حجز مساحة 4-Byte في المعمارية 32-bit ويتم حجز مساحة 8-Byte في المعمارية 64-bit.

في لغة البرمجة C لا توجد الكلمة Pointer كنوع للبيانات كما هو الحال مع باقي الأنواع مثل int أو char، ولكن يتم إضافة رمز النجمة * مع نوع البيانات لتحديد أن هذا المتغير هو مؤشر كما سنتعرف خلال هذا المقال، وسوف تجدني أستخدم المصطلح نوع البيانات Pointer للإشارة إلى ذلك.

لفهم هذه المقالة بشكل جيد يجب أن تكون على علم بـ كيفية التعامل مع المتغيرات وأنواع البيانات في لغة البرمجة C، لذلك يفضل الإطلاع على هذه المقالة قبل التعرف على المؤشرات.

تعتبر المؤشرات من أهم المواضيع التي يجب أن تتعلمها وتفهمها جيدا حتى تصبح مبرمج محترف بلغة الـ C، ولكي تتمكن من فهم المؤشرات يجب أولا أن تتعرف على المكونات الأساسية للذاكرة.

مكونات الذاكرة العشوائية RAM

تتكون الذاكرة من عدد كبير جدا من الوحدات القابلة لتخزين البيانات الثنائية (إما صفر أو واحد) تسمى هذه الوحدات بـ بت أو bit، ونظرا لصغر حجم هذه الوحدة يتم التعامل مع الذاكرة بوحدة أكبر قليلا تسمى بـ بايت أو Byte وهي مكونة من 8 بت، وتعد وحدة البايت هي أصغر وحدة يمكن حجزها لتخزين المتغيرات في أجهزة الحاسوب.

حتى يتمكن جهاز الحاسوب من التعامل مع الذاكرة يتم تقسيم الذاكرة المتاحة في الجهاز إلى وحدات بايت، ويتم ترقيم كل وحدة برقم مسلسل يبدأ بـ صفر، ويسمى هذا الرقم بعنوان وحدة الذاكرة أو الـ Memory Address، فإذا كان جهاز الحاسوب يحتوي على ذاكرة بقدرة تخزينية واحد كيلو بايت 1KB (وهي قدرة صغيرة جدا فقط لتسهيل الفكرة) في هذه الحالة سوف تحتوي الذاكرة على 1024 بايت بما يعني وجود 1024 عنوان للذاكرة، بحيث يمثل العنوان صفر أول وحدة في الذاكرة والعنوان 1023 آخر عنوان في الذاكرة.

تقيد معمارية الحاسوب عدد عناويين الذاكرة التي يمكن التعامل معها، على سبيل المثال المعمارية 32-bit يمكنها التعامل مع عناوين للذاكرة تصل إلى 4,294,967,296‬ عنوان وهي تساوي 4 جيجا بايت من الذاكرة، هذا العدد من العناويين يمكن تمثيله أو تخزينه في 32 بت أو 4 بايت وهو حجم المتغير من النوع Pointer في هذه المعمارية.

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

الإعلان عن المؤشرات Declaration

يقصد بالإعلان عن المؤشرات أن يتم إنشاء متغير جديد من النوع Pointer، في هذه العملية يتم حجز مساحة في الذاكرة حجمها 4 بايت في المعمارية 32-bit أو 8 بايت في المعمارية 64-bit وعند تخصيص هذه المساحة تكون قيمتها الإفتراضية أي قيمة ثنائية عشوائية موجودة في لحظة تخصيصها للمتغير وتسمى هذه القيمة بـ Garbage أو قمامة، لذلك يفضل تهيئة المتغير الجديد بتخصيص القيمة NULL.

لكي يتم إنشاء متغير من النوع Pointer يجب تحديد نوع البيانات الخاص بالمتغير الذي سوف يتم إنشاء المؤشر ليتعامل معه، ثم يتم إستخدام رمز النجمة * ويسمى بـ Asterisk قبل إسم المتغير، والشكل التالي يمثل القاعدة العامة لإنشاء متغير من النوع Pointer

Data-Type *variable_name;

عند الإعلان عن المؤشر يمكنك كتابة رمز النجمة بأي شكل من الأشكال التالية

char * s1;
chars2;
char *s3;

ولكن يجب أن تعلم أن رمز النجمة مرتبط بإسم المتغير وليس بنوع البيانات، وذلك لتحديد أن هذا المتغير هو مؤشر وليس متغير عادي، فكما تعلم أن لغة البرمجة C تمكنك من إنشاء أكثر من متغير من النفس النوع في جملة واحدة، ويمكنك إنشاء متغير عادي ومتغير مؤشر في نفس الجملة كما في الأمثلة التالية

// i_var is variable, i_ptr is Pointer
int i_var, *i_ptr;
 
// c_ptr is Pointer, c_var is variable
char *c_ptrc_var;

من هذه الأمثلة أردت التأكيد على أن رمز النجمة مرتبط بإسم المتغير وليس بنوع البيانات، وذلك أثناء عملية الإعلان عن المتغير.

عند الإعلان عن متغير من النوع Pointer يجب تحديد نوع البيانات الخاص بالمتغير الآخر الذي سوف يشير إليه وذلك حتى يتمكن مترجم اللغة من تحديد حجم المتغير الآخر.

لضمان عدم حدوث أي أخطاء أثناء التعامل مع المؤشرات يفضل تخصيص القيمة NULL كقيمة إفتراضية أثناء عملية الإعلان عن المؤشر، وذلك لتفادي إستخدام المتغير قبل أن يتم تحديد قيمة فعلية له، فكما تعرفنا أن القيمة NULL لها معنى خاص ولذلك عند محاولة إستخدام المتغير قبل تخصيص قيمته سوف يظهر خطأ لينبهك بذلك.

كما تعرفنا أن القيمة NULL هي مساوية للعنوان صفر في الذاكرة، ولذلك فإن القيمة صفر هي القيمة الوحيدة التي يمكن تخصيصها للمؤشر أثناء الإعلان عنه، والمثال التالي يوضح كيفية تخصيص القيمة الإفتراضية للمؤشر بشكلين هما متساويين في النتيجة

int *ptr = NULL;
 
// هذا الأمر مساوي تماما للأمر السابق
int *ptr = 0;

تحديد عنوان المتغير Address Of

حتى الآن قد تعلمنا أن المؤشر يستخدم ليشير إلى عنوان متغير آخر كقيمة له، ولذلك يجب معرفة كيفية إيجاد عنوان متغير في الذاكرة ليتم تخصيص هذا العنوان كقيمة للمؤشر، ويتم ذلك في لغة البرمجة C عن طريق كتابة الرمز & قبل إسم المتغير الذي سبق الإعلان عنه، ويسمى هذا الرمز بـ Address Of Operator.

المثال التالي هو برنامج متكامل كما شرحنا في مقالة الأكواد الأساسية لكتابة برنامج بلغة البرمجة C هذا البرنامج يقوم بإنشاء متغير ثم يقوم بطباعة قيمة المتغير وطباعة عنوان المتغير عن طريق إستخدام الدالة printf، عند تشغيل الكود في المثال التالي سوف تلاحظ أن عنوان المتغير سوف يتغير في كل مرة تقوم بتشغيل الكود

#include <stdio.h>
#include <stdlib.h>
 
int main() {
  int var = 101;
 
  // طباعة قيمة المتغير
  printf("Value = %d\n"var);
 
  // طباعة عنوان المتغير
  printf("Address = %d\n", &var);
 
  system("pause");
  return 0;
}

لاحظ إستخدام رمز & قبل إسم المتغير في أمر طباعة العنوان &var.

تحديد قيمة المتغير المستخدم مع المؤشر Dereferencing

تعرفنا حتى الآن أن المؤشر يقوم بتخزين عنوان متغير آخر في الذاكرة، وبالتالي عند طباعة متغير من النوع Pointer سوف تحصل على عنوان المتغير الآخر وليس القيمة المخزنة في هذا المتغير، ولكي تتمكن من قراءة القيمة المخزنة للمتغير عن طريق المؤشر نقوم بإستخدام الرمز * قبل إسم المؤشر، ويعرف هذا الرمز في هذه العملية بـ Dereferencing أو إلغاء التأشير.

المثال التالي يوضح كيفية تخصيص قيمة متغير بداخل مؤشر وطباعة عنوان المتغير وقيمته

#include <stdio.h>
#include <stdlib.h>
 
int main() {
  // الإعلان عن متغير ومؤشر في أمر واحد
  int var, *ptr;
 
  // تخصيص القيمة 101 للمتغير
  var = 101;
 
  // المؤشر يشير إلى عنوان المتغير
  ptr = &var;
 
  // طباعة عنوان المتغير عن طريق طباعة المؤشر
  printf("var Address = %d\n"ptr);
 
  // طباعة قيمة المتغير عن طريق إلغاء التأشير للمؤشر
  printf("var Value = %d\n", *ptr);
 
  // تغيير قيمة المتغير عن طريق إستخدام المؤشر
  *ptr = 103;
 
  // طباعة قيمة المتغير الجديدة عن طريق طباعة المتغير نفسه
  printf("var New Value = %d\n"var);
 
  system("pause");
  return 0;
}

عند تشغيل الكود من المثال السابق سوف تحصل على قيمة مختلفة لعنوان المتغير في كل مرة، حيث في كل مرة يتم تشغيل البرنامج يتم تخصيص مساحة في الذاكرة مختلفة عن التي سبق تحديدها وبالتالي تحصل على عنوان مختلف في كل مرة، أما عند إلغاء التأشير عن المؤشر يتم طباعة القيمة 101 والتي سبق تخصيصها للمتغير var، ويمكنك تغيير قيمة المتغير نفسه عن طريق إستخدام المؤشر كما في المثال حيث تم تغيير القيمة الموجودة بداخل المتغير var إلى 103 عن طريق إستخدام إلغاء التأشير مع المؤشر، وللتأكيد أن قيمة المتغير قد تحولت إلى 103 قمنا بطباعة قيمة المتغير نفسه.

لتوضيح هذه العملية، فإن مترجم اللغة عندما يجد رمز الـ * قبل إسم المؤشر يقوم بالذهاب إلى العنوان المخزن بداخل المؤشر ليحصل على القيمة الموجودة في هذا العنوان أو تغييرها، والتي تمثل قراءة الرقم 101 في هذا المثال أو تغيير القيمة إلى 103.

أهمية إستخدام المؤشرات في لغة البرمجة C

تمتاز لغة البرمجة C بقدرتها على التعامل مع الذاكرة بشكل مباشر ما يتيح إنشاء برمجيات ذات كفاءة عالية في إستخدام الذاكرة، ويرجع الفضل بشكل كبير إلى المؤشرات، حيث يمكن أن يتم التعامل مع أي متغير أي كان حجمه بمجرد معرفة عنوانه.

وتستخدم المؤشرات بشكل كبير للتعامل مع المصفوفات، حيث تعتبر المصفوفة نوع من أنواع المؤشرات وبالتالي يتم إستخدام العمليات الحسابية على المؤشرات (Pointer Arithmetic) للتنقل بين عناصر المصفوفة كما سنتعرف عليه لاحقا.

يتم إستخدام المؤشرات لتمرير المتغيرات ذات الحجم الكبير بين الدوال، فعند تمرير أي متغير لأي دالة عادة ما يتم إعادة نسخ المتغير في المساحة المخصصة من الذاكرة للدالة (بإستثناء المصفوفات) وتسبب هذه العملية إهدار للوقت المستغرق في عملية النسخ وإهدار للذاكرة المتاحة حيث يتم نسخ المتغير والإحتفاظ بالمتغير الأصلي أي أن المتغير يوجد في الذاكرة مرتين، وتعرف عملية تمرير المؤشرات للدوال بالتمرير عن طريق المرجع أو العنوان Pass By Reference.

تعتبر المؤشرات موضوع حيوي و أساسي عند التحدث عن هياكل البيانات والتي تعرف بـ Data Structures، حيث تستخدم المؤشرات للربط بين وحدات البيانات أو كما تعرف بـ Nodes.

تستخدم المؤشرات لإنشاء متغيرات ديناميكية أو كما تعرف بـ Dynamic-Variables والتي تمكن المبرمج من إنشاء متغيرات أثناء تشغيل البرنامج عند الحاجة إليها، أو إنشاء مصفوفات بالحجم الذي يحدده مستخدم البرنامج، فكما تعلمنا أن المصفوفات في لغة البرمجة C يجب أن يتم تحديد عدد عناصرها بشكل ثابت أثناء إنشاؤها، ولكن بإستخدام المؤشرات يمكنك إنشاء مصفوفات بأي حجم يحدده المستخدم أثناء التشغيل وهو ما يعرف بـ Dynamic Arrays.

أمثلة على المؤشرات في C

المثال التالي يتم إنشاء متغيرين من النوع int و مؤشر، ويقوم البرنامج بطباعة قيمة المتغيرين بإستخدام المؤشر

#include <stdio.h>
#include <stdlib.h>
 
int main() {
  int num1num2, *ptr;
 
  num1 = 101;
  num2 = 102;
 
  // المؤشر يشير إلى عنوان المتغير الأول
  ptr = &num1;
 
  // طباعة عنوان المتغير الأول عن طريق طباعة المؤشر
  printf("num1 = %d\n", *ptr);
 
  // المؤشر يشير إلى عنوان المتغير الثاني
  ptr = &num2;
 
  // طباعة عنوان المتغير الثاني عن طريق طباعة المؤشر
  printf("num2 = %d\n", *ptr);
 
  system("pause");
  return 0;
}
10559

Discussion

  • عبدالله
    عبدالله
    السلام عليكم ورحمة الله وبركاته
    شكرا جزيرا على هذا الموضوع المبسط والمفيد وقد اوضحتم به الفكرة بسلاسة . تمنياتنا لكم بمزيد من التوفيق والارتقاء في عالم البرمجة الشيق .
    لي طلب ان امكنكم تحقيقه ، وهو كيف استطيع التعامل مع الفوت العربي واجعل لوحة المفاتيح تعمل كما ابرمجها ، يعني كيف ابرمج لوحة المفاتيح لتكتب بالعربي من خلال لغة السي ++ ،
    طبعا توجهت بهذا السؤال لكثير من المبرمجين لكن على قول المصرين ( طنشوا ) يا ترا يعرفون الفكرة ولا يحبون تدريسها ام اصلا لا يعرفونها . يا اخي على اقل تقدير جاوبوا على السؤال نفيا او ايجابا .
    • Reply to عبدالله
    • Captcha
    • Notify me of follow up comments via email.

    • Ahmed Abuelfateh
      Ahmed Abuelfateh
      وعليكم السلام ورحمة الله وبركاته
      الفونت العربي يعمل عن طريق ترميز يونيكود (Unicode)، هذا النوع من الترميز يحتاج إلى حجز مساحة 2 أو 4 بايت لكل حرف، حيث تختلف المساحة المطلوبة على حسب نظام التشغيل ونوع مترجم اللغة (Compiler)، وذلك حتى يسمح بتخزين حروف أكثر من المتاحة في الترميز ASCII والذي يحتاج إلى حجز مساحة 1 بايت لكل حرف.
      عندما تقوم بتعريف متغير من النوع char فإنك تقوم بحجز مساحة 1 بايت لكل حرف، وبالتالي لا يمكنك كتابة الحروف الخاصة باللغة العربية، ولكي تتمكن من كتابة حروف باللغة العربية فإنه يتوجب عليك إستخدام نوع البيانات المناسب لذلك.
      طبقا لمعيار لغة البرمجة C90 فإنه يمكنك إستخدام نوع البيانات wchar_t، وهو نوع البيانات المناسب لتخزين حروف بترميز يوني كود، ولكن هذا النوع من البيانات يعاني من بعض المشكلات في تعريفة لكل مترجم (Compiler)، ولذلك لا يوصى بإستخدامه في البرامج التي يمكن أن تعمل في أكثر من بيئة تشغيل مثل ويندوز و لينكس.
      ولكن في معظم المترجمات الشائعة الإستخدام يمكنك إستخدام هذا النوع من البيانات بشكل آمن.
      هناك مشكلة أخرى قد تواجهك عند التعامل مع الحروف العربية في البرامج التي تعمل في موجه الأوامر (Console Applications)، وهذه المشكلة هي أن شاشة سطر الأوامر تستخدم فونت إفتراضي لا يدعم اللغة العربية، وبالتالي عند عرض الحروف في الشاشة لن تظهر بشكل مناسب، ولحل هذه المشكلة يمكنك التأكد من إختيار فونت يدعم اللغة العربية في شاشة موجه الأوامر.
      تعتبر هذه الإجابة مختصرة بشكل كبير ولكن أتمنى أن أكون قد أوضحت الفكرة بشكل عام، وبإذن الله قريبا سوف أقوم بشرح كيفية التعامل مع الحروف العربية في لغة البرمجة سي.
      • Reply to Ahmed Abuelfateh
      • Captcha
      • Notify me of follow up comments via email.

  • رابح هارب
    رابح هارب
    ما شاء الله تبارك الرحمن
    مشكور أخي على المجهود الذي تبذله ،،، لكنك في شرحك لم تعطي أمثلة على النجمة بعد إسم المتغير ،،، ليتك تضع نتائج console appliction على شكل بنود مدمجة مع الشرح لكي نفهم أكثر
    وجزاك الله خيرا
    • Reply to رابح هارب
    • Captcha
    • Notify me of follow up comments via email.

  • Add new Comment
  • Captcha
  • Notify me of follow up comments via email.