كيفية اكتشاف إشارة const إلى القضايا المؤقتة في التجميع أو وقت التشغيل؟

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

سؤال

لقد وجدت مؤخرًا أن معظم الأخطاء في برامج C ++ الخاصة بي هي نموذج مثل المثال التالي:

#include <iostream>

class Z
{
 public:
 Z(int n) : n(n) {}
 int n;
};

class Y
{
 public:
 Y(const Z& z) : z(z) {}
 const Z& z;
};

class X
{
 public:
 X(const Y& y) : y(y) {}
 Y y;
};

class Big
{
 public:
 Big()
 {
   for (int i = 0; i < 1000; ++i) { a[i] = i + 1000; }
 }
 int a[1000];
};

X get_x() { return X(Y(Z(123))); }

int main()
{
 X x = get_x();
 Big b;
 std::cout << x.y.z.n << std::endl;
}

الإخراج: 1000

أتوقع أن يقوم هذا البرنامج بإخراج 123 (قيمة XYZN المحددة في get_x ()) ولكن إنشاء "Big B" يكتب فوق Z. نتيجة لذلك ، فإن الإشارة إلى z المؤقتة في الكائن y الآن قد تم الكتابة عليها الآن Big B ، وبالتالي الناتج ليس ما أتوقعه.

عندما قمت بتجميع هذا البرنامج باستخدام GCC 4.5 مع الخيار "-wall" ، لم يقدم أي تحذير.

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

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

شكرًا،

كلينتون

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

المحلول

لقد قدمت مثل هذه الحالات في قائمة المراسلات Clang-Dev قبل بضعة أشهر ، لكن لم يكن لدى أحد الوقت للعمل عليها في ذلك الوقت (ولم أفعل ، لسوء الحظ).

يعمل Argyrios Kyrtzidis حاليًا على ذلك ، وهنا آخر تحديث له في هذا الأمر (30 نوفمبر 23H04 بتوقيت جرينتش):

لقد عادت الالتزام السابق ، وحسن الإصلاح بشكل أفضل في http://lists.cs.uiuc.edu/pipermail/cfe-commits/week-of-mon-20101129/036875.html. على سبيل المثال

struct S {   int x; };

int &get_ref() {   S s;   S &s2 = s;   int &x2 = s2.x;   return x2; }

نحن نحصل

t3.cpp:9:10: warning: reference to stack memory associated with local variable 's' returned
  return x2;
         ^~
t3.cpp:8:8: note: binding reference variable 'x2' here
  int &x2 = s2.x;
       ^    ~~
t3.cpp:7:6: note: binding reference variable 's2' here
  S &s2 = s;
     ^    ~
1 warning generated.

فشلت المحاولة السابقة في اختبار الاستضافة الذاتية ، لذلك آمل أن تمر هذه المحاولة. أنا سعيد للغاية Argyrios يبحث فيه على أي حال :)

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

هل يمكنك اختبار الكود الخاص بك مقابل هذا الإصدار من Clang؟ أنا متأكد من أن Argyrios سيقدر التعليقات (سواء تم اكتشافها أم لا).

نصائح أخرى

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

  • تغيير الحجج إلى المراجع غير الممتدة. لن تتطابق القيمة المؤقتة مع نوع مرجعي غير مؤشر.

  • إسقاط المراجع تمامًا في الحالات التي يكون فيها هذا الاحتمال. إذا لم تشير مراجع Const الخاصة بك إلى حالة مشتركة منطقية بين المتصل و Callee (إذا فعلوا هذه المشكلة ، فلن تحدث بشكل متكرر للغاية) ، فربما تم إدراجها في محاولة لتجنب النسخ الساذج لأنواع معقدة. لدى المترجمين الحديثة تحسينات في النسخ المتقدمة التي تجعل قيمة التمرير على نحو فعال مثل مرجع مرار في معظم الحالات ؛ نرى http://cpp-next.com/archive/2009/08/want-peed-pass-by-value لتفسير كبير. من الواضح أن نسخ Ellision لن يتم تنفيذها إذا كنت تمرر القيم إلى وظائف المكتبة الخارجية التي قد تعدل المنشورات ، ولكن إذا كان هذا هو الحال ، فأنت لا تمر بها كمراجع كرسوم أو عن عمد -في الإصدار الأصلي. هذا هو الحل المفضل لدي لأنه يتيح للمترجم قلقًا بشأن تحسين النسخ ويحرصني على القلق بشأن مصادر الخطأ الأخرى في الكود.

  • إذا كان المترجم الخاص بك يدعم مراجع RValue ، فاستخدمها. إذا كان بإمكانك على الأقل تحرير أنواع المعلمات من الوظائف التي تقلق بشأن هذه المشكلة ، فيمكنك تحديد metaclass غلاف مثل SO:

قالب <typename t> class need_ref {

T & Ref_ ؛

عام:

Need_ref (t && x) { / * none * /}

Need_ref (T & X): Ref_ (x) { / * nothing * /}

المشغل T & () {return ref_ ؛ }

};

ثم استبدل وسيطات من النوع T&A مع وسيطات من النوع Need_ref. على سبيل المثال ، إذا حددت ما يلي

مستخدم فئة {

int & z ؛

عام:

user (need_ref <int> arg): z (arg) { / * none * /}

};

بعد ذلك ، يمكنك تفكيك كائن من نوع المستخدم بأمان برمز النموذج "int a = 1 ، b = 2 ؛ المستخدم UA (a) ؛" ، ولكن إذا حاولت تهيئة "مجموع المستخدم (A+B)" أو "المستخدم خمسة (5)" يجب على المترجم الخاص بك إنشاء خطأ مرجعي غير ضروري داخل الإصدار الأول من مُنشئ Need_ref (). من الواضح أن هذه التقنية لا تقتصر على المُنشئين ، ولا تفرض أي وقت تشغيل.

المشكلة هنا هي الرمز

 Y(const Z& z) : z(z) {}

كما تتم تهيئة العضو "Z" مع إشارة إلى المعلمة الرسمية "z". بمجرد إرجاع المنشئ ، يشير المرجع إلى كائن لم يعد صالحًا.

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

راجع للشغل ، من الأفضل تسمية العضو "y :: z" باسم "Y :: MZ" إذا كان ذلك ممكنًا. تعبير "z (z)" ليس جذابًا للغاية

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