المؤشر (Pointer) في الحوسبة أو علوم الحاسوب، هو نوع من أنواع البيانات في بعض لغات البرمجة تمثل قيمته (أو تشير إلى) قيمة أخرى مخزنة في مكان آخر في ذاكرة الحاسوب، وذلك باستخدام عنوان الذاكرة لها. عملية الحصول على القيمة التي يشير إليها المؤشر تسمى تتبع المؤشر dereferencing. يعتبر المؤشر تطبيق بسيط لنوع البيانات العام المرجع برغم أنه مختلف عن الوسيلة المسماة مرجع reference في لغة سي++. تفيد المؤشرات إلى البيانات في تحسين الأداء للعمليات المتكررة مثل تمرير السلاسل الحرفية وتراكيب (بنيات) الأشجار. وتستخدم المؤشرات للدوال في ربط الوسائل methods في البرمجة كائنية التوجه والربط عند وقت التشغيل بمكتبات الربط الديناميكية التي يرمز لها بـ DLL.
في حين أن المؤشرات قد استخدمت لتمثل المراجع عموماً إلا أنها تنطبق بشكل أكثر ملاءمة على تراكيب البيانات التي تسمح واجهتها بوضوح للمؤشر أن يعالج بوصفه عنوان ذاكرة. ولأن المؤشرات تسمح بالوصول الغير محمي بشكل واسع لعناوين الذاكرة فإن هناك بعض الخطورة المصاحبة لاستخدامها.
المؤشرات في تراكيب البيانات
عند القيام بإعداد تراكيب البيانات مثل القوائم والأشجار، فإنه من الضروري استخدام المؤشرات للمساعدة في الطريقة التي تتم بها معالجة البيانات والتحكم بها.
الاستخدامات
إن المؤشرات مدعومة مباشرة ودون قيود في لغات مثل سي، سي++، باسكال ومعظم لغات التجميع. وهي تستخدم أساساً في بناء المراجع والتي تتسع بدورها تقريباً لبناء كل تراكيب البيانات وأيضا في تمرير البيانات بين الأجزاء المختلفة من البرنامج.
في لغات البرمجة المعتمدة على الدوال والتي تستند بشكل كبير إلى القوائم؛ يتم التحكم بالمؤشرات والمراجع من اللغة باستخدام التركيبات الداخلية مثل cons.
عند التعامل مع المصفوفات، فإن عملية البحث الحيوي تشتمل على مرحلة تسمى "حساب العنوان" والتي تشمل بناء مؤشر إلى عنصر البيانات المرغوب في المصفوفة. وفي تراكيب البيانات الأخرى مثل القوائم المرتبطة تستخدم المؤشرات كمراجع لتربط قطعة معينة من الكود بأخرى.
تستخدم المؤشرات في تمرير المعاملات باستخدام مراجع لها، ويكون هذا مفيداً إذا أردنا أن يكون تعديل الدالة للمعامل مرئياً للجزء الذي قام باستدعائها. وهذا مفيد أيضا في إرجاع عدة قيم من دالة.
المؤشرات في لغة سي
الصيغة الأساسية للإعلان عن مؤشر هي:
int *money;
حيث يقوم هذا بالإعلان عن money
كمؤشر إلى عدد صحيح.
وحيث أن محتويات الذاكرة لا يضمن ما هي القيمة التي من الممكن أن تحتويها في لغة السي، فيجب توخي الحذر للتأكد من أن العنوان الذي يشير إليه money
هو عنوان صالح، ولهذا يرى البعض استهلال المؤشر بالقيمة NULL.
int *money = NULL;
عندما يتم تتبع مؤشر له القيمة NULL فحينها يحدث خطأ تشغيل ويتوقف التنفيذ عادة مع segmentation fault. وبعد الإعلان عن مؤشر فإن الخطوة المنطقية التالية هي جعله يشير إلى شيء ما.
int a = 5; int *money = NULL; money = &a;
ويقوم هذا بإسناد عنوان الذاكرة للمتغير الصحيح a إلى قيمة المؤشر money، على سبيل المثال إذا كان المتغير a مخزن في الذاكرة عند الموقع 0x8130 فستكون قيمة money هي 0x8130 بعد الإسناد. ولتتبع المؤشر يتم استخدام علامة النجمة * مرة أخرى:
*money = 8;
ويخبر هذا مترجم الكود بأن يأخذ محتويات المؤشر money والتي هي 0x8130، وينتقل إلى ذلك العنوان ويعين القيمة المخزنة به إلى 8. بعد ذلك إذا تم الدخول إلى a فسنجد قيمته 8.
ربما يكون هذا المثال أكثر وضوحا إذا تم فحص الذاكرة مباشرة. افترض أن a له عنوان الذاكرة 0x8130 وmoney له العنوان 0x8134، وافترض أيضا أن الحاسوب المستخدم هو حاسوب 32بت حيث سعة نوع البيانات int هي 32 بت. فإن الشكل التالي هو ما سيتكون في الذاكرة بعد تنفيذ الكود التالي:
int a = 5; int *money = NULL;
Address Contents 0x8130 0x00000005 0x8134 0x00000000
المؤشر ذو القيمة NULL المعروضة هنا هو 0x00000000. وبتعيين عنوان المتغير a للمؤشر money باستخدام مؤثر التعيين &
money = &a;
وتكون الحصيلة في الذاكرة كما يلي:
Address Contents 0x8130 0x00000005 0x8134 0x00008130
وعند تتبع المؤشر money مثل:
*money = 8;
فإن الحاسوب سيأخذ محتويات المؤشر والتي هي 0x8130 وينتقل إلى ذلك العنوان ويعين القيمة 8 إلى ذلك الموقع وتكون حصيلة الذاكرة كما يلي:
Address Contents 0x8130 0x00000008 0x8134 0x00008130
وكما هو واضح فإن استدعاء قيمة المتغير a سوف ينتج القيمة 8 لأن التعليمة السابقة قامت بتعديل محتويات المتغير a عن طريق المؤشر money.
مصفوفات لغة سي
الخطوة التالية في الحديث عن المؤشرات في لغة السي هي المصفوفات. في لغة سي تتم عملية فهرسة المصفوفات باستخدام حسابات المؤشرات ؛ حيث أن معايير اللغة تتطلب أن يكون
array[i]
مكافئ إلى
*(array + i)
وبهذا في لغة سي يمكن اعتبار المصفوفات كمؤشرات لمناطق متتابعة من الذاكرة (ليس بينها فجوات)، والصيغة المستخدمة في الوصول إلى المصفوفات مماثلة لتلك المستخدمة في تتبع المؤشرات، على سبيل المثال المصفوفة array يمكن الإعلان عنها واستخدامها كما يلي:
int array[5]; /* تعلن عن 5 أعداد صحيحة متتالية (Plauger Standard C 1992) integers */ int *ptr = array; /* يمكن استخدام المصفوفات كمؤشرات */ ptr[0] = 1; /* يمكن فهرسة المؤشرات بصيغة المصفوفات */ *(array + 1) = 2; /* يمكن تتبع المصفوفات بصيغة المؤشرات */
ويقوم هذا بحجز موقع في الذاكرة لخمسة أعداد صحيحة ويعلن عن array كمؤشر لهذا الموقع، ومن الاستخدامات الشائعة الأخرى للمؤشرات هي الإشارة للذاكرة المحجوزة ديناميكيا باستخدام دالة malloc والتي ترجع قطعة متصلة من الذاكرة بما لا يقل عن الحجم المطلوب الذي يمكن استخدامه كمصفوفة.
في حين أن معظم المؤثرات على المؤشرات والمصفوفات متكافئة إلا أنه من المهم أن نذكر أن دالة sizeof سوف تختلف. في هذا المثال sizeof(array)
سوف تكون قيمتها 5*sizeof(int)
أي أن حجم المصفوفة 5×حجم متغير العدد الصحيح، ولكن sizeof(ptr)
تكون قيمته sizeof(int*)
أي حجم المؤشر نفسه.
يمكن الإعلان عن القيم الافتراضية لمصفوفة مثل:
int array[5] = {2,4,3,1,5};
إذا فرضت أن array تقع في الذاكرة بدءً من العنوان 0x1000 على حاسوب 32 بت وترتيب البيانات به little-endian، إذاً سوف تحتوي الذاكرة ما يلي:
0 1 2 3 1000 02 00 00 00 1004 04 00 00 00 1008 03 00 00 00 100C 01 00 00 00 1010 05 00 00 00
ويمثل الشكل 5 أعداد صحيحة 2 و4 و3 و1 و5. هذه الأعداد الصحيحة الخمسة تشغل 32 بت (4بايت)، وكل منها يخزن البايت الأقل أهمية أولاً، لأن بنية الحاسوب little-endian وتخزن بالتوالي بدءً من عنوان الموقع 0x1000.
الصيغة العامة في لغة سي مع المؤشرات هي:
array
تعني 0x1000array+1
تعني 0x1004، لاحظ أن +1 تعني حقيقة إضافة مرة واحدة عدد حجم متغير int أي 4 بايت وليس حرفياً +1.*array
تعني تتبع محتويات array بما يعني اعتبار المحتويات كعنوان الذاكرة (0x1000) والذهاب والبحث عن القيمة في ذلك العنوان (0x1000).array[i]
تعني القسم i من المصفوفة والذي يترجم إلى*(array + i)
.- مثال :
array[3] مماثل إلى:
- (array+3)
يعني
- (0x1000 + 3*sizeof(int))
والذي يخبر المترجم بتتبع القيمة المخزنة في العنوان 0x100C والتي هي في هذه الحالة 0x0001.
القوائم المرتبطة في سي
فيما يلي مثال على تعريف القوائم المرتبطة في لغة سي:
/* القائمة المرتبطة الفارغة تمثل باستخدام NULL أو قيمة إشارة أخرى */ #define EMPTY_LIST NULL struct link { /* بيانات هذا العنصر */ void *data; /* العنصر التالي؛ EMPTY_LIST إذا كان الأخير */ struct link *next; };
التمرير باستخدام مرجع
يمكن استخدام المؤشرات لتمرير المتغيرات باستخدام مراجع لها، بما يسمح لقيمتها بأن تتغير، مثال:
void not_alter(int n) { n = 360; } void alter(int *n) { *n = 120; } void func(void) { int x = 24; not_alter(x); /* x لازالت تساوي 24 */ alter(&x); /* x تساوي الآن 120 */ }