مؤشر (برمجة)
المؤشر (بالإنجليزية: Pointer) هو كائن في بعض لغات البرمجة يُخزَّن فيه عنوانًا لمتغير آخر. يشير المؤشر إلى عنوان الذاكرة المرتبط بالمتغير "b". يُمكن ملاحظة أن في هذا المخطط التفصيلي، تُستخدم بنية الحوسبة نفس مساحة العنوان والبيانات الأولية لكل من المؤشرات وغير المؤشرات، بيد انه هذه ليست هي القضية المُراد التطلع إليها. يُعد المؤشر، في علوم الحاسب الآلي، هو أحد أنواع بيانات لغة البرمجة والذي ترجع قيمته بشكل مباشر إلى (أو «تُشير إلى») قيمة أخرى مُخزنة في مكان آخر في ذاكرة الحاسب الآلي مُستخدمة عنوانها. تحتل المؤشرات في لغات البرمجة عالية المستوى بشكلٍ فعال محل سجلات الأغراض العامة في لغات البرمجة ذات المستويات المتدنية مثل لغة التجميع والأكواد، بيد أنها قد تتواجد داخل الذاكرة المتوفرة. مراجع المؤشر هي عبارة عن مكان في الذاكرة، ويُطلق على عملية الحصول على القيمة التي يُشير إليه المؤشر بـعملية تتبع المؤشر dereferencing. يُعتبر المؤشر تطبيقاً بسيطاً أقل إيجازاً لنوع بيانات المرجع الأكثر إيجازاً. تدعم مختلف لغات البرمجة بعضاً من أنواع المؤشرات، على الرغم من احتواء البعض منها على المزيد من القيود في استخدامها عن غيرها. تُحسن المؤشرات إلى البيانات بشكل ملحوظ من أداء العمليات المتكررة مثل تمرير الحرفية وجداول البحث وجداول الرقابة وتراكيب (بنيات) الأشجار. تُعد المؤشرات في أغلب الأحيان وبشكل خاص أكثر توفيراً للوقت والمساحة وذلك خلال إجراء عمليات النسج وتتبع المؤشرات من ما هو عليه الحال عند إجراء عملية نسخ البيانات والولوج إليها إلى ما تُشير إليه المؤشرات. تُستخدم المؤشرات أيضاً للاحتفاظ بعناوين نقاط الدخول لما يُطلق عليه الروتين الفرعي في البرمجة الإجرائية ولربط وقت التشغيل بمكتبات الربط الديناميكية التي يُرمز لها بالرمز (DLLS). تُستخدم المؤشرات إلى الوظائف في البرمجة كائنية التوجه (أو البرمجة غرضية التوجه) في ربط الوسائل، وغالباً ما يُستخدم ما يُطلق عليه اسم جداول الوسائل العملية virtual method tables. . في حين أن المؤشرات قد استخدمت لتمثل المراجع عموماً إلا أنها تنطبق بشكل أكثر ملاءمة على تراكيب البيانات التي تسمح لجهتها بوضوح للمؤشر بالمعالجة (بشكل حسابي عن طريق حساب المؤشر) بوصفه عنوان ذاكرة، وعلى النقيض منcookie أو الإمكانية capability حيث لا يستحيل ذلك. نظراً لإتاحة المؤشر لكل من الوصول الآمن والغير الآمن إلى عناوين الذاكرة، فان هناك بعض المخاطر المصاحبة لاستخدامها في الحالة الأخيرة بشكل خاص.
تعريفات أساسية
المؤشر
(المؤشر)في علوم الحاسبالآلي: هو أحد أنماط المرجع.
البيانات الأولية (أو الأولية فحسب): هي أي وحدات بيانات يُمكن قراءتها أو كتابتها لذاكرة الحاسب الآلي باستخدام ولوج واحد للذاكرة (على سبيل المثال، كلاًّ من البايت والكلمة كلاهما من الأوليات). تجميع البيانات (أو التجميع فحسب) هي عبارة عن مجموعة من الأوليات التي نُسقت بشكل منطقي في الذاكرة والتي تُعرض مُجمَّعة كوحدة بيانات واحدة (على سبيل المثال، يُمكن أن يتكون التجميع من ثلاثة من وحدات البايت مُنسقة منطقيًّا، القيم التي توضح الثلاث إحداثيات للإشارة في المساحة)؛ عندما يتألف التجميع كلياً من نفس النوع من الأوليات، يُمكن تسمية التجميع بـالمصفوفة array؛ بمعنى أن الكلمة الأولية ذات وحدات البايت المتعددة تُعد مصفوفة من وحدات البايت، وتستخدم بعض البرامج الكلمات بهذه الطريقة. في سياق هذه التعريفات، يُعد البايت أصغر قيمة أولية؛ يُحدد كل عنوان ذاكرة بوحدة بايت مختلفة. يعتبر عنوان الذاكرة أول وحدة من وحدات البايت هو عنوان الذاكرة (أو عنوان الذاكرة الأساسي) لوحدات البايت بالكامل. مؤشر الذاكرة (أو المؤشر فحسب) هو أحد الأوليات، القيمة ما يُراد استخدامه كعنوان ذاكرة؛ يُقال أن المؤشر يُمثل عنوان الذاكرة. كما يُقال أن المؤشر يُشير إلى وحدات البايت [في الذاكرة] عندما تكون قيمة المؤشر هي وحدات البايت الخاصة بعنوان الذاكرة. يُعرف المؤشر بشكل عام على انه أحد أنواع المراجع، ويُقال أن مراجع المؤشر تُخزن وحدات البايت في مكان ما في الذاكرة؛ ويجب للحصول على وحدات البايت هذه إتباع عملية تتبع المؤشر. وتعتبر الصفة التي تُميز المؤشرات عن أنواع المراجع الأخرى هي أن قيمة المؤشر يُمكن تفسيرها كعنوان الذاكرة، ويعتبر ذلك مفهوم «مُتدني المستوى». تخدم المراجع كأحد مستويات الإجراءات غير المباشرة: تُحدد قيمة المؤشر أي من عناوين للذاكرة (أي من المعطيات) التي سوف تُستخدم في العمليات الحسابية. حيث تُمثل الإجراءات غير المباشرة جانباً جوهرياً من الخوارزميات، وغالباً ما يُعبر عن المؤشرات كأحد أنواع البيانات الأساسي في لغات البرمجة؛ في لغات البرمجة المُصنفة بشكلٍ ثابت (أو بشكلٍ قوي)، يُحدد نوع المؤشر نوع وحدات البايت إلى التي يُشير إليها المؤشر.
الجمل المُقتبسة
استخدامها في تراكيب البيانات
يجب توافر مؤشرات للمساعدة في إدارة كيفية تنفيذ التراكيب والتحكم بها عند إعداد تراكيب البيانات مثل القوائم، والصفوف، والأشجار. تُعد مؤشرات البداية ومؤشرات النهاية ومؤشرات التكدس من أمثلة المؤشرات النموذجية. يُمكن لهذه المؤشرات أن تكون إما مطلقة absolute (العنوان المادي الفعلي أو العنوان الواقعي في الذاكرة الواقعية) أو النسبية relative (إزاحة من عنوان البداية المطلق («أساس») التي تستخدم فعلياً عدد أقل من وحدات البايت عنه من العنوان الكامل، ولكن عادة ما يتطلب ذلك عملية حسابية إضافية واحدة للوصول إلى الحل).
يُمكن استخدام معادلين من وحدات البايت، يحتوي على 16 بايت، أعداد صحيحة، لتقديم معالجة نسبية لأكثر من 64 كيلو بايت من تراكيب البيانات. يُمكن أن يمتد ذلك إلى 128 كيلو بايت، أو 256 كيلو بايت، أو 512 كيلو بايت فإذا كان العنوان المُشار إليه لابد وأن يكون عند حد نصف كلمة، أو كلمة أو كلمة مزدوجة (لكن تتطلب عملية «تحويل يسار shift left» إضافية عملية بتwise – من خلال 1 أو 2 أو 3 بايت – وذلك من أجل ضبط المُعادل من خلال العامل المكون من 2 أو 3 أو 4 بايت وذلك قبل إضافته إلى العنوان الأساسي). يُفضل بوجه عام، على الرغم من احتواء مثل هذه الأنظمة على الكثير من المشكلات، وكذلك من اجل التيسير على المُبرمج، توافر مساحة العنوان المحددة.
يُمكن استخدام معادل بايت واحد، مثل قيمة ASCII العشرية للرموز (مثل X"29") للإشارة إلى قيمة صحيحة بديلة (أو مؤشر) في مصفوفة (مثل X"01"). يُمكن بهذه الطريقة أن تترجم الرموز على نحو كاف من «البيانات الأولية» إلى مؤشر متسلسل قابل للاستخدام ومن ثم إلى عنوان مطلق من دون جدول البحث.
استخدامها في جداول التحكم
عادة ما تزيد جداول التحكم المُستخدمة في التحكم في تدفق البرنامج من استخدام المؤشرات. عادة ما تكون المؤشرات جزءاً لا يتجزأ من مدخل الجدول، كما انه يُمكن أن تُستخدم على سبيل المثال للإبقاء على نقاط الإدخال لإتمام عملية تنفيذ الروتين الفرعي، معتمدة في ذلك على عدة حالات محددة مُعرَّفة في مدخل الجدول ذاته. يُمكن فهرست المؤشرات ببساطة إلى أخرى منفصلة، لكن مرتبطة، تشكل الجداول مصفوفة من العناوين الفعلية أو العناوين نفسها (اعتماداً على بنيات وتراكيب لغة البرمجة المتوفرة). يُمكن كذلك استخدامها للإشارة (إلى الخلف) إلى مداخل الجدول السابقة (كما في حلقة المعالجة) أو إلى الإمام لتخطي بعض مداخل الجدول (كما في التحويل switch
أو الخروج «المبكر» من الحلقة). نظراً لهذا الغرض الأخير، يُمكن أن يُصبح «المؤشر» ببساطة رقم مدخل الجدول نفسه ويُمكن أن يتحول إلى عنوان فعلي من خلال عملية حسابية بسيطة.
أصول المعمارية
المؤشرات هي عبارة عن فكرة تجريدية صغيرة جدا تحتل قمة إمكانيات المعالجة أو العنونة المُدعمة من قبل معظم المعماريات الحديثة. يُخصص أحد العناوين أو أحد المؤشرات الرقمية في أبسط المخططات لكل وحدة من وحدات الذاكرة في النظام، حيث تكون الوحدات إما البايت أو الكلمة، تُحول الذاكرة ككل بشكل فعال إلى مصفوفة كبيرة جداً. ومن ثم، إذا توفر لدينا العنوان، فسوف يُوفر النظام عملية لاستعادة القيمة المخزنة في وحدة الذاكرة في هذا العنوان (عادة ما تُستخدم أجهزة مسجلات الأغراض العامة).
عادة ما يكون المؤشر كبير بدرجة كافية لاحتواء المزيد من العناوين أكثر من وحدات الذاكرة الموجودة في النظام. يُقدم ذلك احتمالية محاولة البرنامج من الولوج إلى أحد العناوين الذي لا يتطابق مع أي من وحدات الذاكرة، ويرجع ذلك إما لعدم تثبيت مساحة كافية من الذاكرة (على سبيل المثال تخطي مدى الذاكرة المتوفرة) أو لعدم دعم المعمارية لمثل هذه العناوين. يُمكن أن يُطلق على الحالة الأولى، في بعض البرامج مثل معمارية أنتل x86، اسم خطأ التجزئة أو التخصيص segmentation fault (عادة ما تُختصر ب segfault). الحالة الثانية ممكنة في عمليات التنفيذ الحالية لـ AMD64، بحيث يصل طول المؤشرات إلى 64 بت ويصل امتداد العناوين إلى 48 بت فقط. هناك، يجب أن تتفق المؤشرات مع قواعد محددة (العناوين المُتعارف عليها canonical addresses)، لذا فانه في حالة تتبع المؤشر غير المُتعارف عليها، يظهر المعالج خطأ حماية عام.
على الصعيد الآخر، تحتوي بعض الأنظمة على المزيد من وحدات الذاكرة أكثر من ما تحتويه من عناوين. في هذه الحالة، يعمل نظام أكثر تعقيداً مثل أنظمة تخصيص أو تقطيع الذاكرة memory segmentation أو الصفحات المخزنة paging المُوظفة لاستخدام أجزاء مختلفة من الذاكرة في أوقات مختلفة. تدعم الأمثلة الأخيرة للمعمارية x86 إلى 36 بت من عناوين الذاكرة الفيزيائية، والتي نُظمت لمساحة العنوان الخطي 32 بت من خلال أجهزة الصفحات المخزنة أو التصفح PAE. لذا، يُمكن فقط الولوج إلى 1\16 من الذاكرة الكلية المتاحة في المرة الواحدة. ومثال آخر في نفس عائلة جهاز الحاسب الآلي كان وضع الحماية الـ 16 بايت من المعالج 80286، الذي يمكنه، على الرغم من دعم 16 ميجا بايت فقط من الذاكرة الفيزيائية، الولوج إلى 1جيجا بايت من الذاكرة التخيلية، ولكن مزيج من عنوان الـ 16 بيت والسجلات المجزأة جعلت ولوج أكثر من 64 كيلو بايت في ثقل بنية بيانات واحدة. قد تُصبح بعض قيود المؤشر الحسابي ANSI مناسبة لنماذج الذاكرة المجزأة لعائلة هذا المعالج.
دعمت بعض معماريات ذاكرة التخزين المُستخدمة في عمليات الإدخال والإخراج (memory-mapped I/O) والتي تُمكن بعض العناوين من الإشارة إلى وحدات من الذاكرة في حين تُشير أُخرى إلى مسجلات جهاز خاصة بأجهزة أخرى داخل الحاسب الآلي وذلك لتوفير واجهة متناسقة. تظهر بعض المفاهيم المشابهة مثل إزاحة الملف ومؤشرات المصفوفة ومراجع الكائن البعيد التي تخدم بعض من الأغراض المماثلة مثل العناوين لأنواع أخرى من الكائنات.
الاستخدامات
تُدعم المؤشرات بشكل مباشر بدون وجود قيود في لغات برمجة مثل PL/I وC وC++ وPascal وأغلبية لغات التجميع. كما أنها تُستخدم بشكل أساس لتراكيب المراجع، والتي بدورها خطوة أساسية لبناء كل تراكيب البيانات تقريباً، تماماً كما يحدث في عملية تمرير البيانات بين الأجزاء المختلفة من البرنامج.
في اللغات البرمجة الوظيفية التي كثيراً ما تعتمد على القوائم، يتم التحكم بالمؤشرات والمراجع بشكل نظري من خلال اللغة باستخدام التراكيب الداخلية مثل cons.
عند التعامل مع المصفوفات، عادة ما تنطوي عملية البحث الحيوي على مرحلة تُدعى «حساب العنوان address calculation» والتي تتضمن بناء مؤشر كائن البيانات المطلوبة في المصفوفة. إذا كانت عناصر البيانات في المصفوفة لها الأطوال المخصصة من خلال طاقات ثنائية، عادة ما تكون هذه العمليات الحسابية أكثر كفاءة. كثيراً ما تستخدم البطانة أو الحشو Padding كآلية لضمان أن هذه هي الحالة المطلوبة، على الرغم من زيادة متطلبات الذاكرة. في تراكيب البيانات الأخرى، مثل القوائم المرتبطة، تستخدم المؤشرات كمراجع لربط أحد أجزاء التراكيب بالآخر.
تستخدم المؤشرات لتمرير المعاملات باستخدام مراجع لها. ويكون هذا مفيداً إذا أراد المبرمج أن يكون تعديل الدالة للمعامل مرئيا للجزء الذي قام باستدعائها. ويُفيد ذلك أيضا في استرجاع العديد من القيم من الدالة. يُمكن استخدام المؤشرات أيضاً الذاكرة المخصصة وغير المخصصة للمتغيرات الديناميكية والمصفوفات داخل الذاكرة. في أغلب الأحيان ما يتحول المتغير إلى كائن زائد عن الحاجة بعد تحقيق الغرض منه، فإن الإبقاء عليه إهداراً للذاكرة، ولذا فمن الأفضل عدم تخصيصه أو توزيعه (مستخدماً مرجع المؤشر الأصلي) عندما انتهاء الحاجة إليه. قد يؤدي عدم القيام بهذه الخطوة إلى تسرب الذاكرة memory leak (حيث تقل مساحة الذاكرة الفارغة بشكل تدريجي، أو في الحالات الخطير بشكل سريعً، ويرجع ذلك إلى تراكم أعداد كبير من قوالب الذاكرة).
المؤشر في لغة السي "C "
الصيغة الأساسية للإعلان عن مؤشر هي:
int *money;
يقوم هذا بالإشارة إلى القيمة " money" كمؤشر إلى عدد صحيح. حيث أن محتويات الذاكرة لا تضمن ما هي القيمة المحتمل أن تحتويها في لغة السي C، لذا يجب الحرص على التأكد من أن العنوان الذي يُشير إليه " 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;
العنوان المحتويات 0x8130 0x00000005 0x8134 0x00000000
المؤشر ذو القيمة NULL المعروضة هنا هو 0x00000000. وبتعيين عنوان المتغير a للمؤشر money باستخدام مؤثر التعيين &
money = &a;
يكون الناتج في الذاكرة كما يلي:
العنوان المحتويات 0x8130 0x00000005 0x8134 0x00008130
Then by dereferencing money
by coding
*money = 8;
سيأخذ الحاسب الآلي محتويات المؤشر والتي هي 0x8130 وينتقل إلى ذلك العنوان ويعين القيمة 8 إلى ذلك الموقع ويكون الناتج في الذاكرة كما يلي:
العنوان المحتويات 0x8130 0x00000008 0x8134 0x00008130
وكما هو واضح فإن استدعاء قيمة المتغير a سوف ينتج القيمة 8 لأن التعليمة السابقة قامت بتعديل محتويات المتغير a عن طريق المؤشر money.
مصفوفات لغة السي C
تتم فهرسة المصفوفة في لغة السي C بشكل أساسي باستخدام المؤشر الحسابي، حيث أن معايير اللغة التي تتطلب أن يكون معادل إلى *(array+i).[2] لذا يُمكن اعتبار المصفوفات كمؤشرات لمناطق متتابعة من الذاكرة (بدون أي فجوات[2])، والصيغة المُستخدمة للولوج إلى المصفوفات مماثلة لتلك التي يُمكن أن تُستخدم في عملية تتبع المؤشر، ومثال على ذلك يُمكن الإعلان عن المصفوفة array واستخدامها على النحو التالي:
int array[5]; /* Declares 5 contiguous (per Plauger Standard C 1992) integers */
int *ptr = array; /* Arrays can be used as pointers */
ptr[0] = 1; /* Pointers can be indexed with array syntax */
*(array + 1) = 2; /* Arrays can be dereferenced with pointer syntax */
*(1 + array) = 3; /* Pointer addition is commutative */
2[array] = 4; /* Subscript operator is commutative */
بقوم هذا بحجز مكان خاص لخمسة أعداد صحيحة ويُطلق على هذا القالب اسم المصفوفة التي تعمل كمؤشر لهذا المكان. من الاستخدامات الشائعة أيضاً للمؤشرات هي الإشارة للذاكرة المحجوزة ديناميكياً باستخدام دالة malloc التي تُعيد قالب متصل من الذاكرة بحيث لا يقل حجمه عن الحجم المطلوب الذي يُمكن استخدامه كمصفوفة. بينما معظم المؤثرات على المصفوفات والمؤشرات متكافئة إلا أنه من المهم الإشارة إلى أن دالة sizeof سوف تكون مختلف. في هذا المثال سوف تكون قيمة (المصفوفة) sizeof يُساوي 5*sizeof (int) (حجم المصفوفة)، بينما سوف تكون قيمة sizeof (ptr) تساوي sizeof (int) أي حجم المؤشر نفسه. يُمكن الإعلان عن القيم الافتراضية للمصفوفة مثل:
int array[5] = {2,4,3,1,5};
إذا افترضت أن المصفوفة array تقع في الذاكرة بدايةً من العنوان 0x1000 على جهاز الحاسب الآلي 32 بايت وترتيب البيانات داخله little-andian فسوف تحتوي الذاكرة على التالي: (كون القيم الموجودة في نظام عد السادس عشري hexadecimal، مثل تلك الموجودة في العناوين):
0 1 2 3 1000 2 0 0 0 1004 4 0 0 0 1008 3 0 0 0 100C 1 0 0 0 1010 5 0 0 0
يُوضح الشكل خمسة أعداد صحيحة 1 و2 و3 و4 و5. تشغل هذه الأرقام الخمسة 32 بت (أي 4 بايت) ويًخزن كل منها البايت الأقل أهمية أولاً (وهذه هي بنية المعالجة المركزية little-endian) وتُخزن بشكل متتالي بدءً من العنوان 0x1000. الصيغة المُستخدمة في لغة السي C مع المؤشرات هي:
- المصفوفة array تعني 0x1000
- المصفوفة array +1 تعني 0x1004 (لاحظ أن معنى "+1" هو إضافة مرة واحدة عدد حجم متغير int ولا يُقصد بذلك المعني الحرفي ("+ 1")
- المصفوفة *array تعني تتبع محتويات المصفوفة array. بمعني اعتبار هذه المحتويات كعنوان الذاكرة (0x1000) والبحث عن القيمة في هذا العنوان (0x1000).
- array[i] تعني القسم i من المصفوفة والذي يترجم إلى *(array + i)
يوضح المثال الأخير طريقة الولوج إلى محتويات المصفوفة. وتفتيتها:
- Array + i هو موقع الذاكرة الخاص بعامل المصفوفة th(i+1)
*(array + i)
يأخذها عنوان الذاكرة لوصول إلى القيمة.
على سبيل المثال: array[3] يترادف مع *(array+3) يعنى *(0x1000 +3*sizeof(int))، والذي يُعلن «تتبع القيمة المخزنة في 0x100C» في هذه الحالة تكون القيمة 0x0001.
القوائم المرتبطة في لغة السي C
فيما يلي مثال يوضح تعريف القوائم المرتبطة في لغة السي C.
/* القائمة المرتبطة الفارغة تمثل باستخدام NULL أو قيمة إشارة أخرى */
#تعريف القائمة المرتبطة الفارغة
struct link {
void *data; /* بيانات هذا الكائن */
الموقع التالي *next; /* الكائن التالي؛ EMPTY_LIST قائمة فارغة إذا كان الأخير */
};
يُمكن ملاحظة أن تعريف المؤشر التكراري هذا هو نفسه تعريف المرجع التكراري من لغة البرمجة Haskell: Data link a = Nil |Cons a (link a) Nil هي القائمة الفارغة و Cons هي (Link a) هي خلية cons من النوع a مع رابط آخر من النوع a. مع ذلك التعريف باستخدام المراجع تُفحص ولا تستخدم قيم الإشارة التي تحتمل أن تكون مربكة. لهذا السبب، عادةً ما يتم التعامل مع هيكل البيانات في لغة C عن طريق وظائف التضمين، والتي فُحصت بعناية للتأكد من صحتها.
التمرير باستخدام المؤشرات
يُمكن استخدام المؤشرات لتمرير المتغيرات باستخدام مراجع لها، بما يسمح لقيمتها بأن تتغير، على سبيل المثال انظر إلى كود لغة C++ التالي:
/* a copy of the int n is changed */
void not_alter(int n) {
n = 360;
}
/* the actual variable passed (by address) is changed */
void alter(int *n) {
*n = 120;
}
void func(void) {
int x = 24;
/*pass x's address as the argument*/
alter(&x);
/* x now equal to 120 */
not_alter(x);
/* x still equal to 120 */
}
تخصيص الذاكرة الديناميكية
تُستخدم المؤشرات في تخزين عناوين القوالب الذاكرة المخصصة ديناميكياً وإدارتها كذلك. كما تُستخدم مثل هذه القوالب أيضا في تخزين كائنات البيانات أو كائنات المصفوفات. توفر معظم اللغات المُنشاة المُنظمة واللغات غرضية التوجه مساحة من الذاكرة يُطلق عليها الكومة heap أو منطقة التخزين الحر، حيث يتم تخصيص الدوال ديناميكياً.
يوضح مثال التالي لكود لغة السي C طريقة بناء الكائنات ديناميكياً طريقة تزويدها بالمراجع. تدعم مكتبة لغة السي C القياسية وظيفة دالة malloc() لتخصيص قوالب الذاكرة من الكومة heap. حيث تُسجل حجم الكائن بهدف تخصيصه على هيئة متغيرات ويُعيد المؤشر إلى قالب ذاكرة مقسم في شكل جديد بحيث يكون مُناسب لتخزين الكائن، أو تُعيد مؤشر null في حالة فشل عملية التخصيص.
/* مادة مخزون الأجزاء */
struct Item {
int id; /* رقم الجزء */
char * name; /* اسم الجزء */
float cost; /* التكلفة */
};
/* تخصيص وتهيئة مادة جديدة */
struct Item * make_item(const char *name) {
struct Item * item;
/* تخصيص مادة كائن جديد وإعادة شكله */
item = malloc(sizeof(struct Item));
if (item == NULL)
return NULL;
/* تهيئة أعضاء البند الجديد */
memset(item, 0, sizeof(struct Item));
item->id = -1;
item->name = NULL;
item->cost = 0.0;
/* احفظ نسخة من الاسم في البند الجديد */
item->name = malloc(strlen(name) + 1);
if (item->name == NULL) {
free(item);
return NULL;
}
strcpy(item->name, name);
/* إعادة مادة الكائن المُضاف حديثاً */
return item;
}
يُوضح الكود التالي كيفية عدم تخصيص كائنات الذاكرة ديناميكياً أي إعادتها إلى الكومة heap أو منطقة التخزين الحر. تُوفر المكتبة القياسية للغة C وظيفة free() لعدم تخصيص كقالب الذاكرة المخصصة مسبقاً وإعادتها إلى الكومة heap. \* عدم تخصيص مادة الكائن*\ void destroy_item(struct item *item) { \* البحث عن مؤشر الكائن null *\
if (item == NULL) return;
\* عدم تخصيص سلسلة الاسم المحفوظة داخل المادة *\
if (item->name != NULL) { free(item->name); item->name = NULL; }
\* عدم تخصيص المادة نفسها *\ free(item); }
أجهزة الذاكرة المعنونة
يُمكن استخدام المؤشرات في بعض تراكيب وأبنية الحوسبة للمعالجة الذاكرة أو أجهزة الذاكرة المعنونة بشكل مباشر. تُعد عملية تحديد عناوين للمؤشرات هي أحد الأدوات الهامة خلال برمجة المتحكمات الدقيقة أو المايكروكونترولر microcontrollers. فيما يلي مثال بسيط يُوضح مؤشر من نوع int وتهيئتها لعنوان نظام عد السداسي عشر hexademical وفي هذا المثال سوف تكون القيمة الثابتة هي 0x7FFF:
int *hardware_address = (int *)0x7FFF;
ظهر استخدام لغة BISO بهدف الوصول إلى إمكانيات الفيديو لأجهزة الحاسب الآلي في فترة منتصف الثمانينات بطيء السرعة. عادة ما تُستخدم التطبيقات ذات العرض المكثف للولوج إلى ذاكرة الفيديو CGA وذلك عن طريق تحويل قيمة نظام عد السداسي عشر hexademical الثابتة 0xB8000000 لمؤشر لمصفوفة تتكون من 80 من قيم -bit int 16 بالغير مُشار إليها. تتألف كل قيمة من كود ASCII في الخانة ذات الوزن المنخفض من وحدات البايت، والخانة ذات الألوان ذات وحدات البايت العالية. لذا، لوضع حرف "A" في الصف رقم 5 والعمود رقم 2 باللون الأبيض على الأزرق الساطع، يمكنك كتابة الكود كالتالي:
#define VID ((unsigned short (*)[80])0xB8000000)
void foo() {
VID[4][1] = 0x1F00 | 'A';
}
المؤشرات والتحويل القسري
في العديد من اللغات، يوجد في بعض المؤشرات قيد إضافي ألا وهو أن الكائن المُشار إليه كائن من نوع خاص. على سبيل المثال، ربما يُعلن عن المؤشر للإشارة إلى عدد صحيح؛ لذا ستحاول اللغة منع المبرمج من الإشارة إلى كائنات الأعداد بخلاف العدد الصحيح، مثل الأرقام العشرية، والتخلص من بعض الأخطاء. على سبيل المثال في لغة السي C
int *money;
char *bags;
سيكون money مؤشر لعدد صحيح و bags ستكون مؤشر char. سوف يُظهر الناتج التالي تحذير مترجم بان «مهمة من نوع مؤشر غير متجانس» وفقاً للغة GCC
bags = money;
نظراً لإعلان money و bags بأنواع مختلفة. يجب توضيح رغبتك الفعلية في عمل المهمة عن طريق typecasting وتعني " كيفية تغيير نوع البيانات من نوع إلي نوع أخر وقت البرمجة بدون التقييد بنوع معين وبدون التغيير في قيمتها إلا في حالات معينة فقط. للتوضيح " وذلك لمنع ظهور هذا التحذير للمترجم.
bags = (char *)money;
يُوضح ذلك تغيير أو تحويل مؤشر العدد الصحيح لـ money إلى مؤشر char والإشارة إلى bags. يجب أن تحتفظ مسودة 2005 للمتطلبات القياسية للغة السي C التي تقوم بتحويل مؤشر مستمد من أحد الأنواع إلى نوع آخر على الحيادية الصحيحة لكلا النوعين (مؤشرات 6.3.2.3، par.7)[3]
char *external_buffer = "abcdef";
int *internal_data;
internal_data = (int *)external_buffer; // UNDEFINED BEHAVIOUR if "the resulting pointer
// is not correctly aligned"
تضع حسابات المؤشرات في الحسبان حجم النوع في اللغات التي تُمكن المؤشر الحسابي في اللغات التي تُمكن المؤشر الحسابي. على سبيل المثال، ينتج عن إضافة عدد صحيح إلى المؤشر إلى ظهور مؤشر جديد يُشير إلى عنوان أعلى بمقدار مضاعفة هذا الرقم لحجم هذا النوع. يُمكننا ذلك من حساب عنوان عناصر المصفوفة لهذا النوع المحدد بسهولة ويُسر كما وُضح سابقاً في مثال مصفوفات لغة السي C. يجب على المبرمج أن يتوقع أن المؤشر الحسابي سيتم حسابه بطريقة مختلفة وذلك عند تحويله لأحد مؤشرات أحد الأنواع إلى مؤشر من نوع أخر من حجم مختلف. على سبيل المثال إذا بدأت مصفوفة في لغة السي C بmoney عند 0x2000 وsizeof(int) 4 بايت حيث sizeof(char) 1 بايت ومن ثم (money+1) سوف يُشير إلى 0x2004 ولكن سوف بُشير (bags+1) إلى 0x2001. ويُعد فقدان البيانات عند كتابة البيانات «الكبيرة» في محل البيانات «الصغيرة» من مخاطر التحويل القسري (على سبيل المثال bags[0]=65537;)، تظهر نتائج غير متوقعة عند تحويل القيم bit-shifting، ومشكلات المقارنة، خاصة مع القيم في مقابل القيم الغير موقعة.
تقوم بعض اللغات بتخزين معلومات نوع التشغيل التي يُمكن استخدامها للتأكد من أن هذه الأشكال الهامة صالحة في عملية التشغيل على الرغم من استحالة تحديدها أي من هذه الأشكال آمنة في آن واحد وذلك بوجه عام، في حين ترفض اللغات الأخرى القيمة التقريبية الغير قابلة للتغير من الأشكال الآمنة، أو لا تقبل أي منها على الإطلاق.
زيادة مستوى أمان المؤشرات
تسمح المؤشرات بوقوع العديد من أخطار البرمجة ويرجع ذلك لسماحها لبرنامج بالولوج إلى الكائنات التي لم يُعلن عنها بوضوح من قبل. بيد أن السلطة التي توفرها كبيرة جداً لدرجة أنه يُمكن أن يكون من الصعب القيام ببعض مهام البرمجة في غيابها. لذا فقد اختلقت بعض اللغات كائنات لها بعض خصائص المؤشرات المفيدة في حين تجنب بعض ثغراتها pitfalls للمساعدة في التعامل مع مثل هذه المشكلات. تواجه المؤشرات مشكلة رئيسية ألا وهي أنه أثناء معالجتها بشكل مباشر كأرقام، يُمكن تشكيلها للإشارة إلى العناوين غير المستخدمة أو إلى البيانات التي تستخدم لأغراض أخرى. العديد من اللغات، بما في ذلك معظم لغات البرمجة الوظيفية functional programming languages واللغات الأمرية الحديثة imperative languages مثل لغة Java إلي تستبدل المؤشرات بنوع أكثر تعقيداً من المرجع والذي عادة ما يُشار إليه ببساطة كالمرجع والذي يمكن استخدامه فقط للإشارة إلى الكائنات ولا تُعالج كأرقام لمنع هذا النوع من الأخطاء. تُتناول فهرسة المصفوفة كحالة خاصة. يُطلق على المؤشر الذي لا يحتوي على أي عنوان محدد له اسم المؤشر البدائي wild pointer. يمكن لأي محاولة لاستخدام مثل هذه المؤشرات الغير مهيأة أن تؤدي إلى سلوك غير متوقع إما لأن القيمة المبدئية ليست عنوان صالح وإما لأن استخدامها يمكنه تدمير أجزاء أخرى من البرنامج. وغالباً ما تكون النتيجة ظهور خطأ التجزئة segmentation fault أو انتهاك قواعد التخزينstorage violation.
في الأنظمة تخصيص الذاكرة، من السهل بناء المؤشر التابع dangling pointer عن طريق عدم تخصيص منطقة الذاكرة التي تُشير إليها. يُعد هذا النوع من المؤشرات خطير ودقيق لأن منطقة الذاكرة الغير مخصصة يُمكنها أن تحتوي على نفس البيانات التي كانت بها قبل إلغاء تخصيصها ولكن يُمكن حينها إعادة تخصيصها والكتابة عليها من خلال استخدام كود غير متصل بها، غير معروف للكود السابق. يُزعم أن اللغات التي تحتوي على garbage collection تمنع هذا النوع من الأخطاء (حيث تتم عملية إلغاء التخصيص يتم بشكل تلقائي) ولكن لا تتم إزالة المؤشر نفسه بواسطة garbage collector ويمكن أن يشير إلى بيانات ذات صلة وغير متوقعة إذا أعيد استخدامها في أي وقت بعد إلغاء تخصيصها.
تُدعم بعض اللغات مثل لغة C++ المؤشرات الذكية smart pointers والتي تستخدم نموذج مبسط من حساب المرجع للمساعدة في تتبع تخصيص الذاكرة الديناميكية بالإضافة إلى العمل كمرجع. في غياب حلقات المرجع، حيث يشير الكائن إلى نفسه بصورة غير مباشرة من خلال سلسلة من المؤشرات الذكية smart pointers, يلغي هذا على إمكانية المؤشرات التابعة dangling pointer وتسريبات الذاكرة. تدعم سلاسل Delphi حساب المرجع محلياً.
مؤشر Null
غالباً ما يحتوي مؤشر Nullعلى قيمة محددة، ولكنها ليست بالضرورة أن تكون القيمة صفر، توضح أنه لا يشير إلى أي كائن. تُستخدم مؤشرات Null روتينياً، خاصة في لغة C ولغة C++ حيث يستخدم compile-time constant (بالرغم من تفضيل الأعداد الصحيح للصفر في لغة C++ [4] إلا أن لغة C++0x أدخلت nullptr constant لاستخدامه كبديل)، لتوضيح ظروف مثل القصور في successor للكائن الأخير في القائمة المرتبطة linked list، مع الحفاظ على بنية متسقة لنقاط القائمة. هذا الاستخدام لمؤشرات Null يمكن مقارنتها مع أنواع nullable ومع قيم null في قواعد البيانات الارتباطية ومع قيم Nothing في option type. قد اخترعت مراجع Nullable من قبل C.A.R. Hoare في عام 1965 كجزء من لغة Algol W. وقد وصف Hoare اختراعه لاحقاً (2009) بأنه «خطأ بمليار دولار»:[5][6]
لقد أطلقت عليه خطأ المليار دولار الخاص بي. كان اختراع مرجع null في عام 1965. في هذا الوقت، كنت أصمم أول نظام النوع الشامل للمراجع في اللغة غرضية التوجه (ALGOL W). كان هدفي من ذلك هو التأكيد على أن كل استخدامات المراجع لابد وأن تكون آمنة، مع التحقق من التنفيذ التلقائي عن طريق المجمع. ولكن لم أستطع مقاومة الإغراءات لوضع مرجع null ببساطة لأن تنفيذه كان سهل جداً. أدى هذا إلى عدد لا يحصى من الأخطاء ونقاط الضعف ووقوع أعطال في النظام والتي ربما تسببت في مليار دولار من الألم والضرر في الأربعين سنة الماضية.
نظراً لعدم إشارة مؤشر null إلى أي كائن ذات معنى، فإن محاولة تتبع مؤشر null عادة ما يتسبب في ظهور خطأ بالتشغيل. إذا لم يتم التعامل مع هذا الخطأ فإن البرنامج يتوقف عن العمل على الفورً. في حالة لغة C على كمبيوتر عام، يتوقف التنفيذ من خطأ التجزئة segmentation fault لأن عنوان null الحرفي لا يقسم أبداً لبرنامج قيد التشغيل (يُمكن حدوث العديد من الأشياء مع برنامج لغة C في الأنظمة المدمجة embedded system). في لغة Java يشغل الولوج إلى مرجع null NullPointerException التي يُمكن الحصول عليها من خلال كود معالجة الخطأ، ولكن التطبيق المفضل هو التأكد من عدم حدوث هذه الاستثناءات مطلقاً. في اللغات الآمنة يمكن استبدال مؤشر null المحتمل بوحدات مرتبطة tagged union التي تفرض معالجة واضحة للحالة الاستثنائية؛ في الحقيقة يمكن رؤية مؤشر null على أنه مؤشر tagged pointer بإضافة محسوبة. توفر اللغات الأخرى، مثل لغة Objective-C، عملية إرسال الرسائل إلى عنوان nil (قيمة المؤشر الذي لا يشير إلى كائن صالح) دون التسبب في توقف البرنامج عن العمل؛ وسيتم تجاهل الرسالة ببساطة والقيمة المعادة (إذا وجدت) تكون صفر أو 0، اعتماداً على النوع.[7]
يُضمن في برمجة لغة C ولغة C++، المقارنة العادلة بين مؤشرين null؛ تضمن لغة ANSI C أن أي مؤشر null يساوي 0 في مقارنة مع نوع العدد الصحيح؛ علاوة على ذلك يُعرَّف macro NULL على أنه مؤشر null ثابت، قيمته 0 (إما كأحد أنواع الأعداد الصحيح أو المحولة إلى مؤشر لـ void)، لذا سيقارن مؤشر null بالتساوي مع NULL.
وينبغي عدم الخلط بين مؤشر null والمؤشر الغير مهيأ: يُسمح لمؤشر null لمقارنة الغير متكافئ مع أي مؤشر صالح، بينما وفقاً للغة وتنفيذ المؤشر غير المهيأ قد تكون إما قيمة غير محددة (عشوائية أو قيمة بلا معنى) أو يمكن تهيئتها لثابت أولي (محتملة لكن ليس بالضرورة NULL).
تُعيد دالة malloc مؤشر null في معظم بيئات برمجة لغة السي C إذا لم يكن قادراً على تخصيص منطقة الذاكرة المطلوبة، والتي تنبه المتصل بعدم وجود ذاكرة كافية. بالرغم من ذلك بعض تطبيقات malloc(0) مع إعادة مؤشر null وبدلا من ذلك تحدد الفشل عن طريق إعادة مؤشر null وإدخال errno إلى قيمة مناسبة.
تقوم أنظمة الحاسب الآلي على tagged architecture القادرة على التفريق في الأجهزة بين تتبع Null والمحاولة المشروعة للولوج إلى كلمة أو بناء على العنوان صفر. في بعض بيئات لغة البرمجة (على الأقل تطبيق Lisp على سبيل المثال) القيمة المستخدمة على مؤشر null (يطلق عليها اسم nil في Lisp) ربما يكون مؤشر لقالب من البيانات الداخلية المفيدة للتطبيق (لكن لا يمكن الوصول إليها بشكل جلي من خلال برامج المستخدم)، لذا يُفيد السماح باستخدام نفس العدد الصحيح كثابت وكذلك الطريقة السريعة للولوج إلى تطبيقات التنفيذ. يُعرف هذا بأنه ناقل nil.
مؤشر Autorelative
مؤشر autorelative (أو self-relative) هو المؤشر الذي تُفسر قيمته كإزاحة من عنوان المؤشر نفسه؛ لذا، إذا كان هيكل البيانات M له مؤشر autorelative فالكائن p الذي يُشير إلى جزء من M نفسه فإن M ربما يعيد تخصيصها في الذاكرة من دون الحاجة إلى تحديث قيمة p.[8]
مؤشر الارتكاز Based
مؤشر الارتكاز هو المؤشر الذي قيمته عبارة عن إزاحة من قيمة مؤشر آخر. يمكن استخدامه لتخزين وتحميل كتل من البيانات، محددة عنوان بداية الكتلة إلى مؤشر الارتكاز.[9]
المراوغة المزدوجة
في بعض اللغات يمكن أن يشير المؤشر إلى مؤشر آخر، يتطلب عمليتان للتتبع للوصول للقيمة الأصلية. بينما كل مستوى من المراوغة ربما يضيف تكلفة أداء، وأحياناً ضرورية لتقديم سلوك صحيح لهياكل البيانات المعقدة. على سبيل المثال، في لغة C تُعرَّف القائمة المرتبطة من حيث الكائن الذي يحتوي مؤشر للكائن التالي في القائمة:
struct element
{
struct element * next;
int value;
};
struct element * head = NULL;
يستخدم هذا التطبيق مؤشر للكائن الأول في القائمة كبديل للقائمة ككل. إذا أضيفت قيمة جديدة إلى بداية القائمة يجب تغيير المقدمة للإشارة إلى الكائن الجديد. بما أن براهين C تمرر عادة بالقيمة، يتيح استخدام المراوغة المزدوجة بتنفيذ الإدخال بصورة صحيحة، وبه الآثار الجانبية المرغوبة للقضاء على كود الحالة الخاصة للتعامل مع المدخل في أول القائمة:
// Given a sorted list at *head, insert the element item at the first
// location where all earlier elements have lesser or equal value.
void insert(struct element **head, struct element *item)
{
struct element ** p; // p points to a pointer to an element
for (p = head; *p != NULL; p = &(*p)->next)
{
if (item->value <= (*p)->value)
break;
}
item->next = *p;
*p = item;
}
// Caller does this:
insert(&head, item);
في هذه الحالة، إذا كانت قيمة البند أقل من head فإن مقدمة المتصل تُحدَّث بشكل صحيح في عنوان البند الجديد.
المؤشرات البدائية
المؤشرات البدائية هي المؤشرات التي لم يتم تهيأتها بعد (لا يوجد عنوان محدد للمؤشر البدائي) ربما تتسبب مثل هذه المؤشرات في تعطيل البرنامج أو العمل بطريقة بدائية. تُشير المؤشرات التي لم يتم تهيأتها في لغات برمجة Pascal أو لغة السي C إلى عناوين غير متوقعة في الذاكرة.
يوضح مثال الكود التالي مؤشر بدائي:
int func(void)
{
char *p1 = malloc(sizeof(char)); /* (undefined) value of some place on the heap */
char *p2; /* wild (uninitialized) pointer */
*p1 = 'a'; /* This is OK, assuming malloc() has not returned NULL. */
*p2 = 'b'; /* This invokes undefined behavior */
}
هنا، ربما تشير p2 إلى أي مكان في الذاكرة، لذا أداء المهمة *p2 = ‘b’ سيفسد منطقة غير معروفة من الذاكرة التي ربما تحتوي على بيانات حساسة.
الشعبة البدائية
حيث يستخدم المؤشر كعنوان لنقطة الدخول لبرنامج أو بداية لروتين كما أنه يكون إما غير مهيأ أو فاسد، إذا تم الاتصال أو القفز مع ذلك إلى هذا العنوان، فيقال أنه تفسد «الشعبة البدائية». والعواقب دائماً غير متوقعة وربما يظهر الخطأ نفسه بعدة طرق مختلفة اعتماداً على إذا كان المؤشر عنوان صالح أم لا وإذا كان هناك (من قبيل الصدفة) إرشاد صالح (شفرة تشغيل) على هذا العنوان. يمكن أن يوضح اكتشاف الشعبة البدائية أحد أصعب عمليات التصحيح وأكثرها إحباطاً حيث أنه ربما تم بالفعل تدمير كثير من الأدلة من قبل أو عن طريق تنفيذ واحد أو أكثر من التعليمات الغير مناسبة في موقع الشعبة. في حالة ما إذا كان متاحاً، يمكن ألا تكون محاكاة مجموعة التعليمات عادة تضبط فقط الشعبة البدائية قبل دخولها حيز التنفيذ، لكن تقدم أيضاً تتبع كامل أو جزئي لتاريخها.
المحاكاة باستخدام فهرس المصفوفة
من الممكن محاكاة سلوك المؤشر باستخدام فهرس المصفوفة (عادة بعد واحد). بدايةً للغات التي لا تدعم المؤشرات بوضوح ولكن تدعم المصفوفات، يمكن اعتبار المصفوفة ومعالجتها كما لو كانت نطاق الذاكرة بالكامل (في نطاق مصفوفة محددة) وأي فهرس يمكن اعتباره مكافئ لسجل الغرض العام في لغة التجميع (يشير هذا إلى البايت الفردي ولكن التي تناسب قيمته الفعلية لبداية المصفوفة، ليس عنوانه المطلق في الذاكرة). على افتراض أن المصفوفة هي هيكل بيانات 16 ميجا بايت مترابط، وحدات البايت الفردي (أو سلسلة من وحدات البايت المرتبطة في المصفوفة) يمكن عنونتها مباشرة ومعالجتها باستخدام اسم المصفوفة مع العدد الصحيح 31 بايت الغير موقع كمؤشر المحاكاة (يماثل هذا أمثلة مصفوفات اللغة C الموضحة سابقاً). يمكن محاكاة المؤشر الحسابي بالإضافة أو الطرح من الفهرس، مع الحد الأدنى من الإضافات العامة التي تُقارن بالمؤشر الحسابي الحقيقي. حتى انه من الممكن نظرياً، استخدام التقنية السابقة مع جهاز المحاكاة مجموعة الإرشادات لمحاكاة أي كود آلة أو الوسطية (كود البايت) لأي معالج/ لغة في لغة أخرى التي لا تدعم المؤشرات مطلقاً (على سبيل المثال، لغة Java / لغة JavaScript). لتحقيق هذا، يمكن تحميل الشفرة الثنائية بدايةً إلى وحدات بايت مرتبطة من المصفوفة لمحاكاة «القراءة»، التفسير والعمل كلياً مع الذاكرة المحتوية على نفس المصفوفة. إذا كان ضرورياً، لتجنب مشاكل تجاوز سعة المخزن كليةً يمكن للتدقيق الفائق العمل للمجمع (أو إذا لم يكن، كتابة الكود في جهاز المحاكاة)
الدعم في العديد من لغات البرمجة
لغة Ada
Ada هي لغة كتابة قوية حيث أن جميع المؤشرات تكتب ويسمح فقط بالمحادثة المكتوبة الآمنة. تهيأ جميع المؤشرات افتراضياً إلى null، وأي محاولة للولوج إلى البيانات عن طريق مؤشر null يسبب ظهور استثناء. تسمى المؤشرات في لغة Ada بأنماط الولوج. لا تسمح لغة Ada 83 بالحسابات على أنماط الولوج (على الرغم من أن العديد من الموردين المقدمين لها كميزة غير معيارية)، لكن لغة Ada 95 تُدعم الحسابات «الآمنة» على أنماط الولوج عبر حزمة System.Storage_Elements.
لغة Basic
لا تدعم لغة Basic المؤشرات بصورة عامة. اللهجات الحديثة في لغة Basic، مثل FreeBASIC أو BlitzMax لديها تطبيق شامل للمؤشر. في FreeBASIC، تُعامل حسابات مؤشرات ANY (تعادل void* في لغة C) كما لو كان مؤشرANY نطاقه 1 بايت. لا يمكن تتبع مؤشرات ANY، كما في لغة C. أيضاً، الإلقاء بين ANY وأي مؤشر من نوع آخر من المؤشرات لن يسبب أي مخاطر.
dim as integer f = 257
dim as any ptr g = @f
dim as integer ptr i = g
assert(*i = 257)
assert((g + 4) = (@f + 1))
لغة C ولغة C++
المؤشرات في لغة C ولغة C++ هي المتغيرات التي تخزن العناوين ويمكن أن تكون null. يسير كل مؤشر إلى نوع خاص به، ولكن يمكن للفرد التنقل بحرية بين أنواع المؤشرات، على الرغم من أن السلوك هو تطبيق معروف. يسمح نوع خاص من المؤشرات يطلق عليه «مؤشر void» بالإشارة إلى أي نوع من المتغيرات، ولكن تحده حقيقة أنه لا يمكن تتبعه مباشرةً. غالباً يمكن معالجة العنوان نفسه مباشرة بإلقاء مؤشر من وإلى نوع متكامل بحجم مناسب، على الرغم من أن النتائج هي تطبيقات معروفة ويمكن أن تتسبب في سلوكيات غير معروفة؛ بينما لا يوجد لدى معايير لغة C السابقة نوع متكامل الذي ضُمن أن يكون كبير بدرجة كافية، لغة C99 تحدد اسم uintptr_t typedef المعرَّفة في <stdint.h>، لكن لا يحتاج التطبيق لتوافرها.
تدعم لغة ++C بالكامل مؤشرات لغة السيC وتحويل نوع المتغير typecasting لغة C. كما تدعم مجموعة جديدة من مشغلات التحويل نوع المتغير typecasting للمساعدة في التقاط بعض الاشكال الخطيرة في الوقت الواحد. كما تقدم المكتبة المعيارية للغة C++ auto ptr نوع من المؤشرات الذكية الذي يمكن استخدامه في بعض المواقف كبديل آمن للمؤشرات الأولية في لغة C. كما تدعم لغة C++ شكل آخر من المرجع، مختلف عن المؤشر يطلق عليه ببساطة المرجع أو نمط المرجع.
المؤشر الحسابي هو القدرة على تعديل عنوان المؤشر المستهدف بعمليات حسابية (وكذلك مقارنات الحجم) ويقتصر على معايير اللغة للبقاء ضمن حدود كائن المصفوفة الواحدة (أو فقط بعد ذلك)، على الرغم من أن العديد من الهياكل المتكاملة ستسمح بالمزيد من التساهلات الحسابية. الإضافة أو الطرح من مؤشر يحركه بمضاعفات الحجم من نوع البيانات التي تشير إليها. على سبيل المثال، إضافة 1 إلى مؤشر قيمته 4 بايت سيزيد المؤشر بمقدار 4. وهذا له تأثير في زيادة المؤشر للإشارة إلى الكائن التالي في المصفوفة المرتبطة من الأعداد الصحية- غالباً ما تكون هذه هي النتيجة المطلوبة. لا يمكن للمؤشر الحسابي الأداء على مؤشر void لأن نوع void ليس له حجم، وبالتالي لا يمكن إضافة العنوان المشار إليه، عل الرغم من أن gcc وغيرها من المجمعات ستؤدي حسابات البايت على void* كامتداد غير معياري. للعمل مباشرةً مع وحدات البايت يلقوا عادةً المؤشرات إلى BYTE*، أو char* غير الموقعة إذا لم تُعرَّف BYTE في المكتبة المعيارية المستخدمة.
يقدم المؤشر الحسابي للمبرمج طريقة واحدة للتعامل مع مختلف الأنواع: إضافة وطرح رقم الكائن المطلوب بدلاً من الإزاحة الفعلية بوحدات البايت. (مع ذلك مؤشر char، يُعرف char بأنه الحصول دائما على حجم بقيمة 1 بايت، يسمح بإزاحة الكائن من المؤشر الحسابي إلى الممارسة ليتساوى مع إزاحة البايت الواحد) على وجه الخصوص، يوضح تعريف لغة Cصراحةً أن الصيغة a[n] حيث أن n-th كائن في المصفوفة a، تكافئ *(a+n) وهو محتوى الكائن المشار إليه بواسطة a+n. يعني هذا أن n[a] يتساوى مع n[a] ويمكن كتابة مثلاً a[3] أو [a]3 سيلج كلاهما إلى الكائن الرابع من المصفوفة A.
بينما يمكن للمؤشر الحسابي أن يكون مصدر bugs الحاسب الآلي. فإنه يميل إلى إرباك المبرمجين المبتدئين، مما يضطرهم إلى سياقات مختلفة: يمكن أن يكون تعبير حسابي عادي أو أن يكون تعبير مؤشر حسابي، وأحياناً من السهل عمل خطأ في أحدهما للآخر. واستجابة لهذا، العديد من لغات الحاسب الآلي الحديثة المتقدمة (على سبيل المثال Java) لا تسمح بالولوج المباشر إلى الذاكرة باستخدام العنوان. كذلك، تتناول لهجة لغة C الآمنة Cyclone العديد من الموضوعات المتعلقة بالمؤشرات. راجع لغة البرمجة C للمزيد من الانتقادات. مؤشر void أو void* يُدعمه ANSI لغة C ولغة C++ كنوع مؤشر عام. مؤشر void يمكنه تخزين عنوان لأي نوع بيانات وفي لغة C يتحول ضمنياً إلى أي نوع آخر من المؤشرات أثناء المهمة، ويجب أن يلقى صراحة إذا كان تتبع المؤشر ضمني. K&R تستخدم لغة C char* لغرض «المؤشر agnostic» (قبل ANSI C).
int x = 4;
void* q = &x;
int* p = q; /* void* implicitly converted to int*: valid C, but not C++ */
int i = *p;
int j = *(int*)q; /* when dereferencing inline, there is no implicit conversion */
لا تسمح لغة C++ بالتحويل الضمني لـ void* إلى نوع آخر من المؤشرات، حتى أثناء المهمات. كان هذا قرار في التصميم لتجنب الإهمال وحتى الاشكال غير المرغوبة، مع ذلك معظم المجمعين يستخرجون التحذيرات فقط وليس الأخطاء عند مواجهة إلقاءات أخرى سيئة.
int x = 4;
void* q = &x;
// int* p = q; This fails in C++: there is no implicit conversion from void*
int* a = (int*)q; // C-style cast
int* b = static_cast<int*>(q); // C++ cast
في لغة C++، لا يوجد void& (مرجع لـ void) لاستكمال void*، لأن المراجع تتصرف مثل الأسماء المستعارة للمتغيرات التي تشير إليها، ولا يمكن وجود متغير من نوع void.
لغة C#
في لغة برمجة C#، تُدعم للمؤشرات تحت شروط معينة: أي كتلة من الكود المتضمنة المؤشرات لابد من الإشارة عليها بعلامة غير آمن. تتطلب مثل هذه الكتل أذونا أمان أعلى من المسموح بها لتشغيلها. الصيغة هي نفسها الموجودة في لغة C++ والعنوان المشار إليه يمكن أن يكون إما ذاكرة متحكم بها أو غير متحكم بها. مع ذلك، مؤشرات الذاكرة المتحكم بها (أي مؤشر لكائن متحكم به) يجب الإعلان عنها باستخدام كلمة أساسية ثابتة، والتي تمنع garbage collector من تحريك الكائن المشار إليه كجزء من التحكم بالذاكرة بينما يكون المؤشر في النطاق وهذا يحافظ على صلاحية العنوان.
استثناء هذا من استخدام بنية IntPtr وهو تحكم آمن يتساوى مع int* ولا يتطلب كود عدم الأمان. يستعاد هذا النوع غالباً عند استخدام طرق من النظام. Runtime.InteropServices على سبيل المثال:
// Get 16 bytes of memory from the process's unmanaged memory
IntPtr pointer = System.Runtime.InteropServices.Marshal.AllocHGlobal(16);
// Do something with the allocated memory
لغة COBOL
لغة البرمجة COBOL تدعم المؤشرات إلى المتغيرات. عناصر البيانات الولية أو المجمعة (المسجلة) والمعلنة في LINKAGE SECTION من البرنامج التي تقوم طبيعياً على المؤشر مع وجود مساحة لعنوان كائن البيانات (عادة ما تكون كلمة واحدة للذاكرة) مخصصة مثل بند البيانات الحقيقي (ضمني خفي). في كود مصدر البرنامج، تستخدم هذه المتغيرات مثل أي متغير تخزيني آخر لكن محتوياتهم يولج إليها ضمنيا بطرقة غير مباشرة من خلال مؤشراتهم.
مساحة الذاكرة لكل كائن بيانات مشار إليه يقسم ديناميكياً عادةً باستخدام بيانات الاتصال الخارجي أو عن طريق بناء اللغة الموسع المتكامل مثل بيانات EXEC CICS أو EXEC CICS. كما تقدم الإصدارات الممتدة من COBOL متغيرات المؤشر المعلن عنه في شروط استخدام المؤشر. فيم مثل هذه المؤشرات تحددت وعدلت باستخدام بيانات SET و SET ADDRESS. كما تقدم بعض الإصدارات الممتدة من لغة COBOL متغيرات PROCEDURE-POINTER القادرة على تخزين عنوان الكود القابل للتنفيذ.
لغة D
لغة البرمجة D مشتقة من لغة C ولغة C++ وهي تؤيد مؤشرات C وتحويل نوع المتغير typecasting C تأييداً كاملاً.
لغة Eiffel
تدعم اللغة الوجهة Eiffel المؤشرات في شكل المراجع، التي تكتب ولا تسمح بأي شكل من أشكال المؤشر الحسابي. معيار ECMA من لغة Eiffel يتضمن آلية «النوع المرفق» التي تدَّعي ضمان سلامة void.
لغة Fortran
توضح لغة Fortran-90 قدرة المؤشر الكتابي القوية. تحتوي مؤشرات Fortran على أكثر من مجرد عنوان ذاكرة بسيط. كما تنطوي على الحدود العليا والدنيا من أبعاد المصفوفة، الخطوات (على سبيل المثال لدعم أقسام المصفوفة التعسفية)، وغيرها من البيانات الوصفية. المشغل المنتسب، => يستخدم لينسب المؤشر إلى متغير له صفة الهدف. بيانات تخصيص لغة Fortran-90 يمكن استخدامها أيضا لنسب المؤشر إلى كتلة من الذاكرة. على سبيل المثال، يمكن استخدام الكود التالي لتعريف وتكوين هيكل قائمة مرتبطة:
type real_list_t
real :: sample_data(100)
type (real_list_t), pointer :: next => null ()
end type
type (real_list_t), target :: my_real_list
type (real_list_t), pointer :: real_list_temp
real_list_temp => my_real_list
do
read (1,iostat=ioerr) real_list_temp%sample_data
if (ioerr /= 0) exit
allocate (real_list_temp%next)
real_list_temp => real_list_temp%next
end do
تضيف لغة Fortran-2003 دعم للمؤشرات الإجرائية. كذلك، كجزء من ميزة التشغيل المتداخل في لغة C تدعم لغة Fortran-2003 الوظائف الجوهرية لتحويل أنواع مؤشرات C إلى مؤشرات Fortran وإعادتها.
لغة Go
تحتوي لغة Go على مؤشرات. تتماثل صيغتها المعلنة مع مؤشرات لغة C، ولكن تكتب بالعكس، منتهية بالنوع. على عكس لغة C، لدى Go garbage collection ولا تسمح بالمؤشر الحسابي. لا يمكن تعريف أنواع المرجع مثل لغة C++ لكن يتم تمرير بعض الأنواع الضمنية عن طريق المرجع؛ يجب أن تتكون تلك بجعل الكلمة الأساسية بدلاً من الجديدة. كطريقة مختلفة (عن أنواع المرجع) لتوحيد الصيغة بين المؤشر وغير المؤشر، انخفض مشغل السهم (->)والمجمع يعرف فقط أن المبرمج يعني تتبع المؤشر باستخدام مشغل النقطة (.) (بينما على لغة C ولغة C++ سيكون هذا خطأ كامل).
لغة Modula-2
يتم تطبيق المؤشرات كثيرا كما في Pascal، كذلك باراميترات VAR في الإجراءات يطلق عليها Modula-2 وهي أقوى كتابياً من Pascal، مع عدد قليل من الطرق للهروب من نظام الكتابة. بعض المتغيرات في لغة Modula-2 (مثل Modula-3) تحتوي على garbage collection.
لغة Oberon
كثيراً ما تتوافر المؤشرات مع Modula-2. ما زال هناك القليل من الطرق للتهرب من النظام الكتابي وبذلك تكون لغة Oberon ومتغيراتها ما زالت آمنه فيما يتعلق بالمؤشرات أكثر من لغة Modula-2 ومتغيراتها. كما في Modula-3 تمثل garbage collection جزء من مواصفات اللغة.
لغة Pascal
على النقيض من العديد من اللغات التي تميز المؤشرات فإن معيار ISO Pascal يسمح فقط للمؤشرات بالإشارة إلى المؤشرات المكتوبة ديناميكياً والغير معروفة ولا يسمح لهم بالإشارة إلى المعيار الثابت أو المتغيرات المحلية.[10] لا يوجد بها مؤشر حسابي. يجب أن تحتوي المؤشرات أيضاً على نوع مترابط والمؤشر من نوع ما لا يتفق مع المؤشر من نوع آخر (مثل مؤشر char لا يتفق مع مؤشر العدد الصحيح). يساعد هذا على القضاء على القضايا الأمنية المتأصلة تطبيقات المؤشر الآخر، تستخدم هذه خاصة للغة PL/I أو للغة C. كما أنها تزيل بعض المخاطر التي يسببها المؤشر المتدلي، ولكن القدرة على السماح الديناميكي للمساحة المتبعة باستخدام عملية معيار التصرف (الذي له نفس تأثير مثل وظيفة المكتبة الحرة الموجودة في اللغة C) مما يعني أن خطورة مؤشرات التدلي لم يتم القضاء عليها كلياً.[11]
مع ذلك في بعض المصادر التجارية والمفتوحة للغة Pascal (أو مشتقاتها) يسمح بتطبيقات مثل Free Pascal أو Turbo Pascal أو[12] Object Pascal في Embarcadero Delphi – يسمح للمؤشر بالإشارة إلى المعيار الثابت أو المتغيرات المحلية ويمكن الإلقاء من نوع من المؤشرات إلى آخر. علاوة على ذلك، المؤشر الحسابي غير مقيد: الإضافة أو الطرح من المؤشر يحركه بمقدار هذا الرقم من وحدات البايت في كلا الاتجاهين، لكن استخدام إجراءات معيار Inc أو dec معه يحرك المؤشر بحجم نوع البيانات المصرح للإشارة إليه.
لغة البرمجة Perl
تدعم لغة برمجة Perl المؤشرات، على الرغم من ندرة استخدامها، في شكل وظائف من الحزم والتفريغ. تهدف هذه للتفاعلات البسيطة فقط مع مكتبات OS المجمعة. في جميع الحالات الأخرى، تستخدم لغة Perl المراجع المكتوبة والتي لا تسمح بوجود أي شكل من أشكال المؤشر الحسابي. فهي تستخدم لبناء تراكيب البيانات المعقدة.[13]
انظر أيضًا
- مؤشر لدالة
- تجاوز سعة المخزن المؤقت
- تحليل البرنامج الساكن
- متغير (علم الحاسوب)
- Address constant
- Bounded pointer
- تجاوز سعة المخزن المؤقت
- Fat pointer
- مؤشر دالة
- Hazard pointer
- Opaque pointer
- Pointer swizzling
- Reference (computer science)
- تحليل البرنامج الساكن
- Storage violation
- Tagged pointer
- متغير
- حساب ابتداءا من الصفر
- مكرر
المراجع
- دونالد كانوث (1974)، "Structured Programming with go to Statements" (PDF)، Computing Surveys، 6 (4): 261–301، doi:10.1145/356635.356640، مؤرشف من الأصل (PDF) في 3 أغسطس 2013.
- Plauger, P J، ANSI and ISO Standard C Programmer's Reference، Redmond, WA: Microsoft Press، ص. 108, 51، ISBN 1556153597، مؤرشف من الأصل في 16 أبريل 2022،
An array type does not contain additional holes because all other types pack tightly when composed into arrays [at page 51]
{{استشهاد بكتاب}}
: الوسيط author-name-list parameters تكرر أكثر من مرة (مساعدة) - WG14 N1124, C - Approved standards: ISO/IEC 9899 - Programming languages - C, 2005-05-06. نسخة محفوظة 27 ديسمبر 2017 على موقع واي باك مشين.
- Stroustrup, Bjarne (مارس 2001)، "Chapter 5: Pointers, Arrays, and Structures: 5.1.1: Zero"، لغة البرمجة سي++ (كتاب) (ط. 14th printing of 3rd)، United States and Canada: Addison-Wesley، ص. 88، ISBN 0-201-88954-4،
In C, it has been popular to define a macro
NULL
to represent the zero pointer. Because of C++'s tighter type checking, the use of plain 0, rather than any suggestedNULL
macro, leads to fewer problems. If you feel you must defineNULL
. useconst int NULL = 0;
const
qualifier (§5.4) prevents accidental redefinition ofNULL
and ensures thatNULL
can be used where a constant is required.{{استشهاد بكتاب}}
: line feed character في|اقتباس=
في مكان 280 (مساعدة) - Tony Hoare (2009)، "Null References: The Billion Dollar Mistake"، QCon London، مؤرشف من الأصل في 10 أكتوبر 2014.
- توني هور (25 أغسطس 2009)، "Null References: The Billion Dollar Mistake"، InfoQ.com، مؤرشف من الأصل في 28 أبريل 2019.
- The Objective-C 2.0 Programming Language, section "Sending Messages to nil". [وصلة مكسورة] نسخة محفوظة 27 أغسطس 2009 على موقع واي باك مشين.
- us patent 6625718, Steiner, Robert C. (Broomfield, CO), "Pointers that are relative to their own present locations", issued 2003-09-23, assigned to Avaya Technology Corp. (Basking Ridge, NJ)
- "Based Pointers (C++)"، docs.microsoft.com، مؤرشف من الأصل في 04/10/2017.
{{استشهاد ويب}}
: تحقق من التاريخ في:|archivedate=
(مساعدة) - ISO 7185 Pascal Standard (unofficial copy), section 6.4.4 Pointer-types and subsequent. نسخة محفوظة 24 أبريل 2017 على موقع واي باك مشين.
- J. Welsh, W. J. Sneeringer, and C. A. R. Hoare, "Ambiguities and Insecurities in Pascal," Software Practice and Experience 7, pp. 685-696 (1977)
- Free Pascal Language Reference guide, section 3.4 Pointers نسخة محفوظة 28 أبريل 2017 على موقع واي باك مشين.
- "perlref - Perl references and nested data structures - Perldoc Browser"، perldoc.perl.org، مؤرشف من الأصل في 02/01/2018.
{{استشهاد ويب}}
: تحقق من التاريخ في:|archivedate=
(مساعدة)
وصلات خارجية
- Pointers and Memory Introduction to pointers – Stanford Computer Science Education Library
- 0pointer.de A terse list of minimum length source codes that dereference a null pointer in several different programming languages
- "The C book" - containing pointer examples in ANSI C
- بوابة برمجة الحاسوب