هل ستكسر اللغة أو التعليمات البرمجية الموجودة إذا قمنا بإضافة مقارنات آمنة موقعة/غير موقعة مع C/C ++؟

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

سؤال

بعد قراءة هذا السؤال على المقارنات الموقعة/غير الموقعة (تأتي كل يومين أقول):

تساءلت لماذا ليس لدينا مقارنات غير موقعة مناسبة وبدلاً من ذلك هذه الفوضى الرهيبة؟ خذ الإخراج من هذا البرنامج الصغير:

#include <stdio.h>
#define C(T1,T2)\
 {signed   T1 a=-1;\
 unsigned T2 b=1;\
  printf("(signed %5s)%d < (unsigned %5s)%d = %d\n",#T1,(int)a,#T2,(int)b,(a<b));}\

 #define C1(T) printf("%s:%d\n",#T,(int)sizeof(T)); C(T,char);C(T,short);C(T,int);C(T,long);
int main()
{
 C1(char); C1(short); C1(int); C1(long); 
}

تم تجميعها باستخدام برنامج التحويل البرمجي القياسي (GCC ، 64bit) ، أحصل على هذا:

char:1
(signed  char)-1 < (unsigned  char)1 = 1
(signed  char)-1 < (unsigned short)1 = 1
(signed  char)-1 < (unsigned   int)1 = 0
(signed  char)-1 < (unsigned  long)1 = 0
short:2
(signed short)-1 < (unsigned  char)1 = 1
(signed short)-1 < (unsigned short)1 = 1
(signed short)-1 < (unsigned   int)1 = 0
(signed short)-1 < (unsigned  long)1 = 0
int:4
(signed   int)-1 < (unsigned  char)1 = 1
(signed   int)-1 < (unsigned short)1 = 1
(signed   int)-1 < (unsigned   int)1 = 0
(signed   int)-1 < (unsigned  long)1 = 0
long:8
(signed  long)-1 < (unsigned  char)1 = 1
(signed  long)-1 < (unsigned short)1 = 1
(signed  long)-1 < (unsigned   int)1 = 1
(signed  long)-1 < (unsigned  long)1 = 0

إذا قمت بتجميع 32 بت ، فإن النتيجة هي نفسها باستثناء ذلك:

long:4
(signed  long)-1 < (unsigned   int)1 = 0

"كيف؟" من السهل العثور على كل هذا: فقط GOTO القسم 6.3 من معيار C99 أو الفصل 4 من C ++ وحفر الجمل التي تصف كيفية تحويل المعاملات إلى نوع مشترك ، ويمكن أن ينكسر ذلك إذا كان النوع المشترك يعيد انقسام القيم السلبية.

ولكن ماذا عن "لماذا؟". كما نرى ، فإن "<" يفشل في 50 ٪ من جميع الحالات ، كما أنه يعتمد على أحجام الخرسانة لأنواع ، بحيث تعتمد على النظام الأساسي. فيما يلي بعض النقاط التي يجب مراعاتها:

  • عملية تحويل ومقارنة ليست في الحقيقة مثالًا رئيسيًا على قاعدة أقل مفاجأة

  • لا أعتقد أن هناك رمزًا هناك يعتمد على الاقتراح (short)-1 > (unsigned)1 وهو ليس كتبه الإرهابيون.

  • كل هذا أمر فظيع عندما تكون في C ++ مع رمز القالب ، لأنك تحتاج إلى سحر Type Traint لربط "<".


بعد كل شيء ، مقارنة القيمة الموقعة وغير الموقعة لأنواع مختلفة هو سهل التنفيذ:

signed X < unsigned Y -> (a<(X)0) || ((Z)a<(Z)b) where Z=X|Y 

إن الفحص المسبق رخيص ويمكن أيضًا تحسينه من قبل المترجم إذا كان يمكن إثبات وجود A> = 0.

إذن هذا سؤالي:

هل ستكسر اللغة أو التعليمات البرمجية الموجودة إذا قمنا بإضافة مقارنات آمنة موقعة/غير موقعة مع C/C ++؟

("هل تكسر اللغة" يعني أننا سنحتاج إلى إجراء تغييرات ضخمة على أجزاء مختلفة من اللغة لاستيعاب هذا التغيير)


تحديث:لقد قمت بتشغيل هذا على Turbo-C ++ 3.0 القديم الجيد وحصلت على هذا الإخراج:

char:1
(signed  char)-1 < (unsigned  char)1 = 0

لماذا (signed char)-1 < (unsigned char) == 0 هنا؟

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

المحلول

نعم ، سوف يكسر اللغة/الكود الحالي. اللغة ، كما لاحظت ، تحدد بعناية السلوك عند استخدام المعاملات الموقعة وغير الموقعة معًا. هذا السلوك مع عوامل المقارنة ضروري لبعض التعابير المهمة ، مثل:

if (x-'0' < 10U)

ناهيك عن أشياء مثل (مقارنة المساواة):

size_t l = mbrtowc(&wc, s, n, &state);
if (l==-1) ... /* Note that mbrtowc returns (size_t)-1 on failure */

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

نصائح أخرى

إجابتي هي لـ C فقط.

لا يوجد نوع في C يمكنه استيعاب جميع القيم الممكنة لجميع أنواع الأعداد الصحيحة الممكنة. أقرب C99 يأتي إلى هذا هو intmax_t و uintmax_t, ويغطي تقاطعها فقط نصف نطاق كل منها.

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

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

أنا متأكد من أن هناك الكثير من رمز UNIX القديم هناك ، وربما بعض التشغيل على جهازك ، وهذا يفترض ذلك (int)-1 > (unsigned)1. (حسنًا ، ربما كتبه مقاتلو الحرية ؛-)

إذا كنت تريد LISP/Haskell/Python/$ المفضل_

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

يوجد الكثير من الكود المكتوب في C و C ++ مما يمكن أن أتخيله معًا (قد يكون بعضها مكتوبًا من قبل الإرهابيين).

الاعتماد على "اقتراح ذلك (short)-1 > (unsigned)1"قد يتم ذلك عن غير قصد من قبل شخص ما. يوجد الكثير من الكود C الذي يتعامل مع معالجة البتات المعقدة وأشياء مماثلة. من الممكن تمامًا أن يستخدم بعض المبرمجين سلوك المقارنة الحالي في مثل هذا الرمز. (الأشخاص الآخرون قدموا أمثلة لطيفة بالفعل على مثل هذا الرمز ، والرمز أبسط مما أتوقعه).

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

أعتقد أن C ++ يشبه الإمبراطورية الرومانية. إنه كبير ، ومؤسس للغاية لإصلاح الأشياء التي ستدمرها.

C ++ 0x - و Boost - هي أمثلة على بناء جملة فظيعة فظيعة - نوع الطفل فقط يمكن لآباءه أن يحبهما - وهو بعيد جدًا عن الأنيقة البسيطة (ولكن محدودة للغاية) منذ 10 سنوات.

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

وبمجرد الانهيار ، هناك الكثير مما هو مؤهل أيضًا للتثبيت بأثر رجعي.

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

إن وجود أنواع متعددة لكل تنسيق تخزين ، مثل كل من الإصدارات الغلاف وغير المخلص من أعداد صحيحة موقعة وغير موقعة ، يمكن أن تسمح للمترجم بالتمييز بين "أنا أستخدم قيمة 16 بت هنا في حالة جعل الأمور أكثر كفاءة ، لكنها لن تتجاوز النطاق 0-65535 ولن أهتم بما حدث إذا حدث ذلك) "و" أنا أستخدم قيمة 16 بت تحتاج إلى التفاف إلى 65535 تصبح سلبية ". العملية ، ولكن في الحالة السابقة ، يمكن للمترجم أن يحذف ذلك. فيما يتعلق برغبتك الخاصة ، فإن معنى المقارنة بين عدم الرفوف موقعة طويلة و عدم الرفعة سيكون غير موقّع طويلًا واضحًا ، وسيكون من المناسب لمجمول البرمجي أن يولد تسلسل متعدد الإلغاء ضروري لتحقيق ذلك (منذ تحويل الرقم السلبي إلى غير مغامر unsigned long سيكون سلوكًا غير محدد ، حيث يحدد برنامج التحويل البرمجي سلوكًا لمشغلي المقارنة على هذه الأنواع لن يتعارض مع أي شيء آخر يمكن تحديده).

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

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

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