c++ - هل زيادة مؤشر إلى صفيف ديناميكي بحجم 0 غير معرف؟




pointers undefined-behavior (2)

أعتقد أن لديك بالفعل الجواب ؛ إذا نظرت بعمق أكثر: لقد قلت إن زيادة مكرر غير متجانس هو UB وبالتالي: هذه الإجابة هي في ما هو التكرار؟

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

int arr [] = {0،1،2،3،4،5،6،7،8،9}؛

int * p = arr ؛ // p يشير إلى العنصر الأول في arr

++ ع؛ // ع نقاط إلى arr [1]

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

int * e = & arr [10]؛ // مؤشر فقط الماضي العنصر الأخير في arr

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

هذا من الإصدار 5 ++ C ++ بواسطة Lipmann.

لذلك هو UB لا تفعل ذلك.

AFAIK ، على الرغم من أننا لا نستطيع إنشاء مجموعة من الذاكرة الثابتة بحجم 0 ، ولكن يمكننا القيام بذلك بأخرى ديناميكية:

int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined

كما قرأت ، تتصرف p مثل عنصر نهاية الماضي. يمكنني طباعة العنوان الذي يشير إليه p .

if(p)
    cout << p << endl;
  • على الرغم من أنني متأكد من أننا لا نستطيع إلغاء تحديد هذا المؤشر (العنصر الماضي - الأخير) كما لا يمكننا استخدامه مع التكرارات (العنصر الماضي - الأخير) ، لكن ما لست متأكدًا منه هو ما إذا كان يتم زيادة هذا المؤشر p ؟ هل السلوك غير المحدد (UB) يشبه التكرارات؟

    p++; // UB?

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

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

[expr.post.incr] / [expr.pre.incr]
يجب أن يكون المعامل [...] أو مؤشرًا لنوع كائن محدد بالكامل.

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

[basic.compound] 3 ببيان حول نوع المؤشر الذي قد يحتوي عليه المرء ، وبما أنه ليس من بين الثلاثة الآخرين ، فمن الواضح أن نتيجة العملية ستقع تحت 3.4: مؤشر غير صالح .
ومع ذلك ، لا يقول أنه لا يُسمح لك باستخدام مؤشر غير صالح. على العكس من ذلك ، فإنه يسرد بعض الحالات العادية الشائعة (مثل مدة انتهاء التخزين) حيث تصبح المؤشرات غير صالحة بانتظام. لذلك يبدو أن هذا شيء مسموح به. وحقيقة:

[basic.stc] 4
إن السلوك غير المباشر من خلال قيمة مؤشر غير صالحة وتمرير قيمة مؤشر غير صالحة إلى دالة إلغاء تخصيص. أي استخدام آخر لقيمة مؤشر غير صالح له سلوك معرف بالتنفيذ.

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

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

[basic.compound]
تمثل قيمة صالحة لنوع مؤشر كائن إما عنوان بايت في الذاكرة أو مؤشر فارغ. إذا كان كائن من النوع T يقع على عنوان ، يُقال أن [...] يشير إلى هذا الكائن ، بغض النظر عن كيفية الحصول على القيمة .
[ملاحظة: على سبيل المثال ، سيتم اعتبار العنوان الذي يقع بعد نهاية المصفوفة للإشارة إلى كائن غير مرتبط بنوع عنصر المصفوفة الذي قد يكون موجودًا في هذا العنوان. [...]].

اقرأ كـ: حسنًا ، من يهتم! طالما يشير المؤشر إلى مكان ما في الذاكرة ، فأنا جيد؟

[basic.stc.dynamic.safety] قيمة المؤشر هي مؤشر مشتق بأمان [بلاه بلاه]

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

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

أوه ، لذلك قد لا يهم ، فقط ما اعتقدت. ولكن الانتظار ... "قد لا"؟ وهذا يعني ، قد كذلك . كيف أعرف؟

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

انتظر ، حتى أنه من الممكن أنني بحاجة إلى استدعاء declare_reachable() على كل مؤشر؟ كيف أعرف؟

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

[expr.reinterpret.cast] 5
يمكن تحويل قيمة النوع المتكامل أو نوع التعداد بشكل صريح إلى مؤشر. مؤشر تم تحويله إلى عدد صحيح بالحجم الكافي [...] والعودة إلى نفس نوع المؤشر [...] القيمة الأصلية ؛ التعيينات بين المؤشرات والأعداد الصحيحة يتم تعريفها بالتنفيذ.

الصيد

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

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

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





dynamic-arrays