ما هي العوائق التي تحول دون فهم المؤشرات وما الذي يمكن فعله للتغلب عليها؟[مغلق]

StackOverflow https://stackoverflow.com/questions/5727

  •  08-06-2019
  •  | 
  •  

سؤال

لماذا تعتبر المؤشرات عاملاً رئيسياً في إرباك العديد من طلاب المستوى الجامعي الجدد وحتى القدامى في لغة C أو C++؟هل هناك أي أدوات أو عمليات تفكير ساعدتك على فهم كيفية عمل المؤشرات على المتغير والوظيفة وما بعد المستوى؟

ما هي بعض الممارسات الجيدة التي يمكن القيام بها لإيصال شخص ما إلى مستوى "آه، لقد فهمت" دون التورط في المفهوم العام؟في الأساس، قم بالتمرين مثل السيناريوهات.

هل كانت مفيدة؟

المحلول

المؤشرات هي مفهوم يمكن أن يكون مربكًا للكثيرين في البداية، خاصة عندما يتعلق الأمر بنسخ قيم المؤشر مع الاستمرار في الإشارة إلى نفس كتلة الذاكرة.

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

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

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

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


لنفترض أن فئة THouse المستخدمة أدناه تبدو كما يلي:

type
    THouse = class
    private
        FName : array[0..9] of Char;
    public
        constructor Create(name: PChar);
    end;

عند تهيئة كائن المنزل، يتم نسخ الاسم المعطى للمنشئ في الحقل الخاص FName.هناك سبب لتعريفها على أنها مصفوفة ذات حجم ثابت.

في الذاكرة، سيكون هناك بعض النفقات العامة المرتبطة بتخصيص المنزل، وسأوضح ذلك أدناه على النحو التالي:

---[ttttNNNNNNNNNN]---
     ^   ^
     |   |
     |   +- the FName array
     |
     +- overhead

تعتبر منطقة "tttt" منطقة علوية، وعادةً ما يكون هناك المزيد منها لأنواع مختلفة من أوقات التشغيل واللغات، مثل 8 أو 12 بايت.من الضروري ألا تتغير القيم المخزنة في هذه المنطقة أبدًا بواسطة أي شيء آخر غير مخصص الذاكرة أو إجراءات النظام الأساسية، وإلا فإنك تخاطر بتعطل البرنامج.


تخصيص الذاكرة

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

بمعنى آخر، سيختار رجل الأعمال المكان.

THouse.Create('My house');

تخطيط الذاكرة:

---[ttttNNNNNNNNNN]---
    1234My house

احتفظ بمتغير مع العنوان

اكتب عنوان منزلك الجديد على قطعة من الورق.ستكون هذه الورقة بمثابة مرجعك لمنزلك.بدون هذه القطعة من الورق، ستضيع، ولن تتمكن من العثور على المنزل، إلا إذا كنت موجودًا فيه بالفعل.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...

تخطيط الذاكرة:

    h
    v
---[ttttNNNNNNNNNN]---
    1234My house

انسخ قيمة المؤشر

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

ملحوظة عادةً ما يكون هذا هو المفهوم الذي أجد صعوبة في شرحه للناس، المؤشران لا يعنيان كائنين أو كتل ذاكرة.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1
    v
---[ttttNNNNNNNNNN]---
    1234My house
    ^
    h2

تحرير الذاكرة

هدم المنزل.يمكنك بعد ذلك إعادة استخدام الورقة للحصول على عنوان جديد إذا كنت ترغب في ذلك، أو مسحها لتنسى عنوان المنزل الذي لم يعد موجودًا.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    h := nil;

هنا أقوم أولاً ببناء المنزل والحصول على عنوانه.ثم أفعل شيئًا بالمنزل (استخدمه، ...الكود، الذي تم تركه كتمرين للقارئ)، ثم أقوم بتحريره.أخيرًا قمت بمسح العنوان من المتغير الخاص بي.

تخطيط الذاكرة:

    h                        <--+
    v                           +- before free
---[ttttNNNNNNNNNN]---          |
    1234My house             <--+

    h (now points nowhere)   <--+
                                +- after free
----------------------          | (note, memory might still
    xx34My house             <--+  contain some data)

المؤشرات المتدلية

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

var
    h: THouse;
begin
    h := THouse.Create('My house');
    ...
    h.Free;
    ... // forgot to clear h here
    h.OpenFrontDoor; // will most likely fail

استخدام h بعد المكالمة ل .Free قد العمل، ولكن هذا مجرد حظ خالص.على الأرجح أنها سوف تفشل، في مكان العملاء، في منتصف عملية حرجة.

    h                        <--+
    v                           +- before free
---[ttttNNNNNNNNNN]---          |
    1234My house             <--+

    h                        <--+
    v                           +- after free
----------------------          |
    xx34My house             <--+

كما ترون ، لا يزال H يشير إلى بقايا البيانات في الذاكرة ، ولكن نظرًا لأنها قد لا تكون كاملة ، فقد يستخدم استخدامها كما كان من قبل.


تسريب ذاكرة

تفقد قطعة الورق ولا يمكنك العثور على المنزل.لا يزال المنزل قائمًا في مكان ما، وعندما تريد لاحقًا بناء منزل جديد، لا يمكنك إعادة استخدام هذا المكان.

var
    h: THouse;
begin
    h := THouse.Create('My house');
    h := THouse.Create('My house'); // uh-oh, what happened to our first house?
    ...
    h.Free;
    h := nil;

هنا قمنا بالكتابة فوق محتويات ملف h متغير بعنوان البيت الجديد لكن البيت القديم ما زال قائما...مكان ما.بعد هذا الرمز، لا توجد وسيلة للوصول إلى ذلك المنزل، وسوف يبقى قائما.بمعنى آخر، ستظل الذاكرة المخصصة مخصصة حتى يتم إغلاق التطبيق، وعندها سيقوم نظام التشغيل بتمزيقها.

تخطيط الذاكرة بعد التخصيص الأول:

    h
    v
---[ttttNNNNNNNNNN]---
    1234My house

تخطيط الذاكرة بعد التخصيص الثاني:

                       h
                       v
---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN]
    1234My house       5678My house

الطريقة الأكثر شيوعًا للحصول على هذه الطريقة هي نسيان تحرير شيء ما، بدلاً من الكتابة فوقه كما هو مذكور أعلاه.في مصطلحات دلفي، سيحدث هذا بالطريقة التالية:

procedure OpenTheFrontDoorOfANewHouse;
var
    h: THouse;
begin
    h := THouse.Create('My house');
    h.OpenFrontDoor;
    // uh-oh, no .Free here, where does the address go?
end;

بعد تنفيذ هذه الطريقة، لا يوجد مكان في متغيراتنا لوجود عنوان المنزل، ولكن المنزل لا يزال موجودًا.

تخطيط الذاكرة:

    h                        <--+
    v                           +- before losing pointer
---[ttttNNNNNNNNNN]---          |
    1234My house             <--+

    h (now points nowhere)   <--+
                                +- after losing pointer
---[ttttNNNNNNNNNN]---          |
    1234My house             <--+

كما ترون ، يتم ترك البيانات القديمة سليمة في الذاكرة ، ولن يتم إعادة استخدامها بواسطة مخصص الذاكرة.يتابع المخصص أي مناطق الذاكرة التي تم استخدامها ، ولن يعيد استخدامها إلا إذا كنت تحررها.


تحرير الذاكرة مع الاحتفاظ بمرجع (غير صالح الآن).

هدم المنزل، امسح إحدى قطع الورق ولكن لديك أيضًا قطعة ورق أخرى عليها العنوان القديم، عندما تذهب إلى العنوان، لن تجد منزلًا، ولكن قد تجد شيئًا يشبه الآثار من واحد.

ربما ستجد أيضًا منزلاً، لكنه ليس المنزل الذي أعطيت له العنوان في الأصل، وبالتالي فإن أي محاولات لاستخدامه كما لو كان ملكًا لك قد تفشل فشلاً ذريعًا.

في بعض الأحيان قد تجد أن أحد العناوين المجاورة به منزل كبير إلى حد ما يحتل ثلاثة عناوين (الشارع الرئيسي 1-3)، ويتجه عنوانك إلى منتصف المنزل.أي محاولات للتعامل مع هذا الجزء من المنزل الكبير المكون من 3 عناوين كمنزل صغير واحد قد تفشل أيضًا بشكل فظيع.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := h1; // copies the address, not the house
    ...
    h1.Free;
    h1 := nil;
    h2.OpenFrontDoor; // uh-oh, what happened to our house?

وهنا تم هدم المنزل، من خلال الإشارة في h1, ، و في حين h1 وتم تطهيرها كذلك، h2 لا يزال لديه العنوان القديم القديم.قد ينجح أو لا ينجح الوصول إلى المنزل الذي لم يعد قائمًا.

هذا هو الاختلاف في المؤشر المتدلي أعلاه.انظر تخطيط الذاكرة الخاصة به.


تجاوز المخزن المؤقت

تقوم بنقل المزيد من الأشياء إلى المنزل أكثر مما يمكنك استيعابه، مما يؤدي إلى تسربها إلى منزل الجيران أو الفناء.عندما يعود صاحب المنزل المجاور لاحقًا إلى المنزل، سيجد كل أنواع الأشياء التي يعتبرها ملكًا له.

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

وبالتالي هذا الكود:

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('My house');
    h2 := THouse.Create('My other house somewhere');
                         ^-----------------------^
                          longer than 10 characters
                         0123456789 <-- 10 characters

تخطيط الذاكرة بعد التخصيص الأول:

                        h1
                        v
-----------------------[ttttNNNNNNNNNN]
                        5678My house

تخطيط الذاكرة بعد التخصيص الثاني:

    h2                  h1
    v                   v
---[ttttNNNNNNNNNN]----[ttttNNNNNNNNNN]
    1234My other house somewhereouse
                        ^---+--^
                            |
                            +- overwritten

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


القوائم المرتبطة

عندما تتبع عنوانًا على قطعة من الورق، فإنك تصل إلى منزل، وفي ذلك المنزل توجد قطعة أخرى من الورق عليها عنوان جديد، للمنزل التالي في السلسلة، وهكذا.

var
    h1, h2: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;

هنا نقوم بإنشاء رابط من منزلنا إلى مقصورتنا.يمكننا أن نتبع السلسلة حتى لا يوجد منزل NextHouse المرجع، مما يعني أنه الأخير.لزيارة جميع منازلنا، يمكننا استخدام الكود التالي:

var
    h1, h2: THouse;
    h: THouse;
begin
    h1 := THouse.Create('Home');
    h2 := THouse.Create('Cabin');
    h1.NextHouse := h2;
    ...
    h := h1;
    while h <> nil do
    begin
        h.LockAllDoors;
        h.CloseAllWindows;
        h := h.NextHouse;
    end;

تخطيط الذاكرة (تمت إضافة Nexthouse كوصلة في الكائن ، لوحظ مع LLLL الأربعة في الرسم البياني أدناه):

    h1                      h2
    v                       v
---[ttttNNNNNNNNNNLLLL]----[ttttNNNNNNNNNNLLLL]
    1234Home       +        5678Cabin      +
                   |        ^              |
                   +--------+              * (no link)

من الناحية الأساسية، ما هو عنوان الذاكرة؟

عنوان الذاكرة هو في الأساس مجرد رقم.إذا كنت تفكر في الذاكرة كمجموعة كبيرة من البايتات ، فإن البايت الأول يحتوي على العنوان 0 ، والآخر العنوان 1 وما إلى ذلك.وهذا مبسط، ولكنه جيد بما فيه الكفاية.

لذلك تخطيط الذاكرة هذا:

    h1                 h2
    v                  v
---[ttttNNNNNNNNNN]---[ttttNNNNNNNNNN]
    1234My house       5678My house

قد يحتوي على هذين العنوانين (أقصى اليسار - هو العنوان 0):

  • ح1 = 4
  • ح2 = 23

مما يعني أن قائمتنا المرتبطة أعلاه قد تبدو في الواقع كما يلي:

    h1 (=4)                 h2 (=28)
    v                       v
---[ttttNNNNNNNNNNLLLL]----[ttttNNNNNNNNNNLLLL]
    1234Home      0028      5678Cabin     0000
                   |        ^              |
                   +--------+              * (no link)

من المعتاد تخزين عنوان "لا يشير إلى أي مكان" كعنوان صفري.


في المصطلحات الأساسية، ما هو المؤشر؟

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

نصائح أخرى

في أول حصة دراسية لي في Comp Sci، قمنا بالتمرين التالي.صحيح أن هذه كانت قاعة محاضرات بها ما يقرب من 200 طالب ...

يكتب الأستاذ على السبورة: int john;

جون يقف

يكتب الأستاذ: int *sally = &john;

تقف سالي وتشير إلى جون

أستاذ: int *bill = sally;

يقف بيل ويشير إلى جون

أستاذ: int sam;

يقف سام

أستاذ: bill = &sam;

يشير بيل الآن إلى سام.

اظن انك حصلت على الفكرة.أعتقد أننا أمضينا حوالي ساعة في القيام بذلك، حتى استعرضنا أساسيات تعيين المؤشر.

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

السبب الذي يجعل المؤشرات تربك الكثير من الناس هو أنها تأتي في الغالب بخلفية قليلة أو معدومة في هندسة الكمبيوتر.نظرًا لأن الكثيرين لا يبدو أن لديهم فكرة عن كيفية تنفيذ أجهزة الكمبيوتر (الجهاز) فعليًا - فإن العمل في C/C++ يبدو غريبًا.

التمرين هو أن نطلب منهم تنفيذ جهاز افتراضي بسيط يعتمد على الكود الثانوي (في أي لغة يختارونها، تعمل لغة بايثون بشكل رائع لهذا الغرض) مع مجموعة تعليمات تركز على عمليات المؤشر (التحميل، التخزين، العنونة المباشرة/غير المباشرة).ثم اطلب منهم كتابة برامج بسيطة لمجموعة التعليمات تلك.

أي شيء يتطلب أكثر من مجرد إضافة بسيطة سيتضمن مؤشرات ومن المؤكد أنهم سيحصلون عليه.

لماذا تعتبر المؤشرات عاملاً رئيسياً في إرباك العديد من الطلاب الجدد وحتى القدامى على مستوى الكلية في لغة C/C++؟

إن مفهوم العنصر النائب للقيمة - المتغيرات - يتطابق مع شيء تعلمناه في المدرسة - الجبر.لا يوجد تشابه موجود يمكنك رسمه دون فهم كيفية وضع الذاكرة فعليًا داخل الكمبيوتر، ولا أحد يفكر في هذا النوع من الأشياء حتى يتعامل مع أشياء منخفضة المستوى - على مستوى الاتصالات C/C++/byte .

هل هناك أي أدوات أو عمليات تفكير ساعدتك على فهم كيفية عمل المؤشرات على المتغير والوظيفة وما بعد المستوى؟

صناديق العناوين.أتذكر عندما كنت أتعلم برمجة BASIC في الحواسيب الصغيرة، كانت هناك تلك الكتب الجميلة التي تحتوي على ألعاب، وفي بعض الأحيان كان عليك وضع القيم في عناوين معينة.كانت لديهم صورة لمجموعة من الصناديق، تم تصنيفها تدريجيًا بالأرقام 0، 1، 2...وتم التوضيح أن شيئًا صغيرًا واحدًا فقط (بايت) يمكن أن يتناسب مع هذه الصناديق، وكان هناك الكثير منها - بعض أجهزة الكمبيوتر بها ما يصل إلى 65535!كانوا بجانب بعضهم البعض، وكان لديهم جميعا عنوان.

ما هي بعض الممارسات الجيدة التي يمكن القيام بها لإيصال شخص ما إلى مستوى "آه، لقد فهمت" دون التورط في المفهوم العام؟في الأساس، قم بالتمرين مثل السيناريوهات.

من أجل التدريب؟اصنع هيكلًا:

struct {
char a;
char b;
char c;
char d;
} mystruct;
mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;
cout << 'Start: my_pointer = ' << *my_pointer << endl;
my_pointer++;
cout << 'After: my_pointer = ' << *my_pointer << endl;
my_pointer = &mystruct.a;
cout << 'Then: my_pointer = ' << *my_pointer << endl;
my_pointer = my_pointer + 3;
cout << 'End: my_pointer = ' << *my_pointer << endl;

نفس المثال المذكور أعلاه، باستثناء C:

// Same example as above, except in C:
struct {
    char a;
    char b;
    char c;
    char d;
} mystruct;

mystruct.a = 'r';
mystruct.b = 's';
mystruct.c = 't';
mystruct.d = 'u';

char* my_pointer;
my_pointer = &mystruct.b;

printf("Start: my_pointer = %c\n", *my_pointer);
my_pointer++;
printf("After: my_pointer = %c\n", *my_pointer);
my_pointer = &mystruct.a;
printf("Then: my_pointer = %c\n", *my_pointer);
my_pointer = my_pointer + 3;
printf("End: my_pointer = %c\n", *my_pointer);

انتاج:

Start: my_pointer = s
After: my_pointer = t
Then: my_pointer = r
End: my_pointer = u

ربما يفسر ذلك بعض الأساسيات من خلال المثال؟

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

لقد قام شخص آخر بالفعل بربط هذا البرنامج التعليمي، ولكن يمكنني تسليط الضوء على اللحظة التي بدأت فيها فهم المؤشرات:

برنامج تعليمي عن المؤشرات والمصفوفات في لغة C:الفصل 3 - المؤشرات والسلاسل

int puts(const char *s);

في الوقت الراهن، تجاهل const. تم تمرير المعلمة إلى puts() هو المؤشر، هذه هي قيمة المؤشر (نظرًا لأن جميع المعلمات في لغة C يتم تمريرها حسب القيمة)، وقيمة المؤشر هي العنوان الذي يشير إليه، أو ببساطة عنوان. وهكذا عندما نكتب puts(strA); كما رأينا، نقوم بتمرير عنوان strA[0].

في اللحظة التي قرأت فيها هذه الكلمات، تفرقت الغيوم وغمرني شعاع من ضوء الشمس بفهم المؤشر.

حتى لو كنت مطور VB .NET أو C# (مثلي) ولا تستخدم أبدًا تعليمات برمجية غير آمنة، فلا يزال الأمر يستحق فهم كيفية عمل المؤشرات، وإلا فلن تفهم كيفية عمل مراجع الكائنات.بعد ذلك سيكون لديك فكرة شائعة ولكن خاطئة مفادها أن تمرير مرجع كائن إلى طريقة ما ينسخ الكائن.

لقد وجدت "البرنامج التعليمي حول المؤشرات والمصفوفات في لغة C" الخاص بـ Ted Jensen مصدرًا ممتازًا للتعرف على المؤشرات.وهو مقسم إلى 10 دروس، تبدأ بشرح ماهية المؤشرات (وهدفها) وتنتهي بالمؤشرات الوظيفية. http://home.netcom.com/~tjensen/ptr/cpoint.htm

بالانتقال من هناك، يقوم دليل Beej's Guide to Network Programming بتعليم واجهة برمجة التطبيقات الخاصة بمآخذ توصيل Unix، والتي يمكنك من خلالها البدء في القيام بأشياء ممتعة حقًا. http://beej.us/guide/bgnet/

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

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

widget->wazzle.fizzle = fazzle.foozle->wazzle;

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

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

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

اعتقدت أنني سأضيف تشبيهًا لهذه القائمة والذي وجدته مفيدًا جدًا عند شرح المؤشرات (في الماضي) كمدرس لعلوم الكمبيوتر؛أولا، دعونا:


اضبط المسرح:

فكر في موقف للسيارات يحتوي على 3 مساحات، وهذه المساحات مرقمة:

-------------------
|     |     |     |
|  1  |  2  |  3  |
|     |     |     |

وهذا يشبه إلى حد ما مواقع الذاكرة، فهي متسلسلة ومتجاورة.نوع من مثل مجموعة.في الوقت الحالي لا توجد سيارات فيها، لذا فهي مثل مصفوفة فارغة (parking_lot[3] = {0}).


أضف البيانات

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

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |R| | |G| |
| o-o | o-o | o-o |

هذه السيارات كلها من نفس النوع (السيارة)، لذا فإن إحدى الطرق للتفكير في هذا هي أن سياراتنا عبارة عن نوع من البيانات (على سبيل المثال int) لكن لديهم قيم مختلفة (blue, red, green;يمكن أن يكون اللون enum)


أدخل المؤشر

الآن، إذا أخذتك إلى موقف السيارات هذا، وطلبت منك أن تجد لي سيارة زرقاء، فإنك تمد إصبعًا واحدًا وتستخدمه للإشارة إلى سيارة زرقاء في المكان 1.هذا يشبه أخذ المؤشر وتخصيصه لعنوان الذاكرة (int *finger = parking_lot)

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


إعادة تعيين المؤشر

الآن يمكنني أن أطلب منك العثور على سيارة حمراء بدلاً من ذلك ويمكنك إعادة توجيه إصبعك إلى سيارة جديدة.الآن يظهر لي المؤشر (نفس المؤشر السابق) بيانات جديدة (مكان وقوف السيارات حيث يمكن العثور على السيارة الحمراء) من نفس النوع (السيارة).

لم يتغير المؤشر فعليًا، بل لا يزال قائمًا لك إصبعي، فقط البيانات التي كانت تظهر لي تغيرت.(عنوان "مكان وقوف السيارات")


مؤشرات مزدوجة (أو مؤشر إلى مؤشر)

يعمل هذا مع أكثر من مؤشر أيضًا.يمكنني أن أسأل أين هو المؤشر الذي يشير إلى السيارة الحمراء ويمكنك استخدام يدك الأخرى والإشارة بإصبعك إلى الإصبع الأول.(هذا مثل int **finger_two = &finger)

الآن إذا أردت أن أعرف مكان السيارة الزرقاء، يمكنني أن أتبع اتجاه الإصبع الأول إلى الإصبع الثاني، إلى السيارة (البيانات).


المؤشر المتدلي

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

   1     2     3
-------------------
| o=o |     | o=o |
| |B| |     | |G| |
| o-o |     | o-o |

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


المؤشر الحسابي

حسنًا، مازلت تشير إلى مكان ركن السيارة الثاني (الذي تشغله الآن السيارة البرتقالية)

   1     2     3
-------------------
| o=o | o=o | o=o |
| |B| | |O| | |G| |
| o-o | o-o | o-o |

حسنًا، لدي سؤال جديد الآن..أريد أن أعرف لون السيارة في التالي مكان لركن السيارات.يمكنك أن ترى أنك تشير إلى النقطة 2، لذا فما عليك سوى إضافة 1 وأنت تشير إلى النقطة التالية.(finger+1)، والآن بما أنني أردت معرفة البيانات الموجودة هناك، فيجب عليك التحقق من تلك النقطة (وليس الإصبع فقط) حتى تتمكن من احترام المؤشر (*(finger+1)) لمعرفة وجود سيارة خضراء هناك (البيانات الموجودة في ذلك الموقع)

لا أعتقد أن المؤشرات كمفهوم صعبة بشكل خاص - فمعظم النماذج العقلية للطلاب ترسم خريطة لشيء مثل هذا ويمكن أن تساعد بعض الرسومات التخطيطية السريعة.

تكمن الصعوبة، على الأقل تلك التي خبرتها في الماضي ورأيت الآخرين يتعاملون معها، في أن إدارة المؤشرات في C/C++ يمكن أن تكون معقدة بشكل غير ضروري.

يساعد مثال البرنامج التعليمي الذي يحتوي على مجموعة جيدة من الرسوم البيانية بشكل كبير في فهم المؤشرات.

يقدم جويل سبولسكي بعض النقاط الجيدة حول فهم المؤشرات في كتابه دليل حرب العصابات لإجراء المقابلات شرط:

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

أعتقد أن العائق الرئيسي أمام فهم المؤشرات هو المعلمون السيئون.

يتم تعليم الجميع تقريبًا الأكاذيب حول المؤشرات:أنهم كذلك لا شيء أكثر من عناوين الذاكرة, ، أو أنها تسمح لك بالإشارة إلى مواقع تعسفية.

وبالطبع فهي صعبة الفهم وخطيرة وشبه سحرية.

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

حاولت أن أكتب شرحًا لهذا منذ بضعة أشهر في هذا بلوق وظيفة - نأمل أن يساعد شخص ما.

(ملاحظة، قبل أن يتحذلق أي شخص علي، نعم، معيار C++ يقول تلك المؤشرات يمثل عناوين الذاكرة.لكنها لا تقول أن "المؤشرات هي عناوين الذاكرة، ولا شيء سوى عناوين الذاكرة ويمكن استخدامها أو التفكير فيها بالتبادل مع عناوين الذاكرة".التمييز مهم)

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

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

عندما يقول API:

int doIt(char *buffer )
//*buffer is a pointer to the buffer

ماذا يريد؟

يمكن أن تريد:

رقم يمثل عنوانًا للمخزن المؤقت

(لإعطائها ذلك، أقول doIt(mybuffer), ، أو doIt(*myBuffer)?)

رقم يمثل عنوانًا لعنوان مخزن مؤقت

(هل هذا doIt(&mybuffer) أو doIt(mybuffer) أو doIt(*mybuffer)?)

رقم يمثل عنوان العنوان إلى عنوان المخزن المؤقت

(ربما هذا doIt(&mybuffer).أو هو doIt(&&mybuffer) ؟او حتى doIt(&&&mybuffer))

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

"المؤشر" مثقل للغاية.هل المؤشر عنوان لقيمة؟أم أنه متغير يحمل عنوانًا لقيمة ما.عندما تريد دالة مؤشرًا، هل تريد العنوان الذي يحمله متغير المؤشر، أم تريد عنوان متغير المؤشر؟أنا مرتبك.

أعتقد أن ما يجعل المؤشرات صعبة التعلم هو أنه حتى المؤشرات تكون مرتاحًا لفكرة أن "في موقع الذاكرة هذا توجد مجموعة من البتات التي تمثل int، أو double، أو حرفًا، أو أيًا كان".

عندما ترى المؤشر لأول مرة، فإنك لا تحصل حقًا على ما هو موجود في موقع الذاكرة هذا."ماذا تقصد، أنها تحمل عنوان?"

أنا لا أتفق مع فكرة "إما أن تحصل عليها أو لا تحصل عليها".

تصبح أسهل في الفهم عندما تبدأ في العثور على استخدامات حقيقية لها (مثل عدم تمرير الهياكل الكبيرة إلى الوظائف).

السبب وراء صعوبة فهمه ليس لأنه مفهوم صعب ولكن لأنه بناء الجملة غير متناسق.

   int *mypointer;

لقد تعلمت أولاً أن الجزء الموجود في أقصى اليسار من عملية إنشاء المتغير يحدد نوع المتغير.لا يعمل إعلان المؤشر بهذه الطريقة في C وC++.بدلاً من ذلك يقولون أن المتغير يشير إلى النوع الموجود على اليسار.في هذه الحالة: *com.mypointer يشير على كثافة العمليات.

لم أفهم المؤشرات بشكل كامل حتى حاولت استخدامها في C# (مع غير آمن)، فهي تعمل بنفس الطريقة تمامًا ولكن مع بناء جملة منطقي ومتسق.المؤشر هو النوع نفسه.هنا com.mypointer يكون مؤشر إلى int.

  int* mypointer;

لا تجعلني أبدأ حتى في المؤشرات الوظيفية ...

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

يعجبني تشبيه عنوان المنزل، لكني كنت أفكر دائمًا في أن العنوان هو صندوق البريد نفسه.بهذه الطريقة يمكنك تصور مفهوم إلغاء الإشارة إلى المؤشر (فتح صندوق البريد).

على سبيل المثال اتباع قائمة مرتبطة:1) ابدأ بورقتك مع العنوان 2) انتقل إلى العنوان على الورقة 3) افتح صندوق البريد للعثور على ورقة جديدة مع العنوان التالي عليه

في القائمة المرتبطة الخطية، لا يحتوي صندوق البريد الأخير على أي شيء (نهاية القائمة).في القائمة المرتبطة الدائرية، يحتوي صندوق البريد الأخير على عنوان صندوق البريد الأول فيه.

لاحظ أن الخطوة 3 هي المكان الذي يحدث فيه عدم المرجعية والمكان الذي ستتعطل فيه أو تخطئ عندما يكون العنوان غير صالح.بافتراض أنه يمكنك الوصول إلى صندوق البريد الخاص بعنوان غير صالح، تخيل أن هناك ثقبًا أسود أو شيئًا ما يقلب العالم رأسًا على عقب :)

أعتقد أن السبب الرئيسي الذي يجعل الناس يواجهون مشكلة في ذلك هو أنه لا يتم تدريسه بشكل عام بطريقة مثيرة للاهتمام وجذابة.أود أن أرى محاضرًا يحضر 10 متطوعين من الجمهور ويعطيهم مسطرة بطول متر واحد لكل منهم، ويجعلهم يقفون في ترتيب معين ويستخدمون المساطر للإشارة إلى بعضهم البعض.ثم قم بإظهار حساب المؤشر عن طريق تحريك الأشخاص (وحيث يشيرون إلى مساطرهم).ستكون طريقة بسيطة ولكنها فعالة (وقبل كل شيء لا تُنسى) لعرض المفاهيم دون التورط في الآليات.

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

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

أعتقد أنها قد تكون في الواقع مشكلة في بناء الجملة.يبدو بناء جملة C/C++ للمؤشرات غير متناسق وأكثر تعقيدًا مما ينبغي.

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

في بعض الأحيان، لا يمكنك رؤية الغابة حتى تتعلم تجاهل الأشجار.

يأتي الارتباك من طبقات التجريد المتعددة الممزوجة معًا في مفهوم "المؤشر".لا يرتبك المبرمجون بسبب المراجع العادية في Java/Python، لكن المؤشرات تختلف من حيث أنها تكشف عن خصائص بنية الذاكرة الأساسية.

يعد الفصل الواضح بين طبقات التجريد مبدأً جيدًا، لكن المؤشرات لا تفعل ذلك.

الطريقة التي أحببت أن أشرح بها الأمر كانت من حيث المصفوفات والفهارس - قد لا يكون الناس على دراية بالمؤشرات، لكنهم يعرفون عمومًا ما هو الفهرس.

لذلك أقول تخيل أن ذاكرة الوصول العشوائي عبارة عن مصفوفة (ولديك 10 بايت فقط من ذاكرة الوصول العشوائي):

unsigned char RAM[10] = { 10, 14, 4, 3, 2, 1, 20, 19, 50, 9 };

إذن فإن المؤشر إلى المتغير هو في الحقيقة مجرد فهرس (البايت الأول) لذلك المتغير في ذاكرة الوصول العشوائي.

لذلك إذا كان لديك مؤشر/index unsigned char index = 2, ، فمن الواضح أن القيمة هي العنصر الثالث، أو الرقم 4.المؤشر إلى المؤشر هو المكان الذي تأخذ فيه هذا الرقم وتستخدمه كمؤشر بحد ذاته، مثل RAM[RAM[index]].

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

رقم صندوق البريد.

إنها جزء من المعلومات التي تسمح لك بالوصول إلى شيء آخر.

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

ليست طريقة سيئة لفهمها، عبر التكرارات..لكن استمر في البحث وسترى Alexandrescu يبدأ في الشكوى منهم.

العديد من مطوري C++ السابقين (الذين لم يفهموا أبدًا أن التكرارات هي مؤشر حديث قبل التخلص من اللغة) ينتقلون إلى C# وما زالوا يعتقدون أن لديهم تكرارات جيدة.

حسنًا، المشكلة هي أن كل ما هو مكرر هو على خلاف تام مع ما تحاول منصات وقت التشغيل (Java/CLR) تحقيقه:استخدام جديد وبسيط للجميع.والذي يمكن أن يكون جيدًا، لكنهم قالوا ذلك مرة واحدة في الكتاب الأرجواني وقالوه حتى قبل C وقبله:

المراوغة.

مفهوم قوي جدًا ولكن ليس كذلك أبدًا إذا قمت بذلك على طول الطريق..تعتبر التكرارات مفيدة لأنها تساعد في تجريد الخوارزميات، مثال آخر.ووقت الترجمة هو المكان المناسب للخوارزمية، وهو بسيط جدًا.أنت تعرف الكود + البيانات، أو بتلك اللغة الأخرى C#:

IEnumerable + LINQ + Massive Framework = 300 ميجابايت من عقوبة وقت التشغيل غير المباشرة للتطبيقات الرديئة وسحبها عبر أكوام من مثيلات الأنواع المرجعية.

"Le Pointer رخيص."

أكدت بعض الإجابات أعلاه أن "المؤشرات ليست صعبة حقًا" ، لكن لم تستمر في معالجة مباشرة حيث "المؤشر صعب!" يأتي من.منذ عدة سنوات مضت، قمت بتدريس طلاب السنة الأولى في مجال علوم الكمبيوتر (لمدة عام واحد فقط، حيث أنني كنت سيئًا للغاية في ذلك) وكان من الواضح لي أن فكرة المؤشر ليس بالأمر الصعب.ما هو الصعب هو الفهم لماذا ومتى تريد المؤشر.

لا أعتقد أنه يمكنك فصل هذا السؤال - لماذا ومتى يتم استخدام المؤشر - عن شرح مشكلات هندسة البرمجيات الأوسع.لماذا يجب على كل متغير لا يكون متغيرًا عالميًا، ولماذا يجب على المرء أن يأخذ كودًا مشابهًا في الدوال (التي، احصل على هذا، استخدم مؤشرات لتخصيص سلوكهم لموقع الاتصال الخاص بهم).

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

int* my_int_pointer;

يقول أن my_int_pointer يحتوي على عنوان الموقع الذي يحتوي على int.

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

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

http://arjay.bc.ca/Modula-2/Text/Ch15/Ch15.8.html#15.8.5 يتحدث عنها بشكل أكثر تماسكًا مني.:-)

كل مبتدئ في C/C++ لديه نفس المشكلة ولا تحدث هذه المشكلة بسبب "صعوبة تعلم المؤشرات" ولكن "من وكيف يتم شرحها".يقوم بعض المتعلمين بجمعها لفظيًا وبعضها بصريًا وأفضل طريقة لشرحها هي الاستخدام مثال "القطار". (يناسب المثال اللفظي والبصري).

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top