هل لدي خطأ في تحسين دول مجلس التعاون الخليجي أو مشكلة في رمز C؟

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

  •  01-07-2019
  •  | 
  •  

سؤال

اختبر الكود التالي:

#include <stdio.h>
#include <stdlib.h>
main()
{
    const char *yytext="0";
    const float f=(float)atof(yytext);
    size_t t = *((size_t*)&f);
    printf("t should be 0 but is %d\n", t);
}

اجمعها مع:

gcc -O3 test.c

المخرج الجيد يجب أن يكون:

"t should be 0 but is 0"

ولكن مع دول مجلس التعاون الخليجي 4.1.3، لدي:

"t should be 0 but is -1209357172"
هل كانت مفيدة؟

المحلول

استخدم علامة المترجم -fno-strict-aliasing.

مع تمكين الاسم المستعار الصارم، كما هو الحال افتراضيًا لـ -O3 على الأقل، في السطر:

size_t t = *((size_t*)&f);

يفترض المترجم أن size_t* لا يشير إلى نفس منطقة الذاكرة مثل float*.بقدر ما أعرف، هذا سلوك متوافق مع المعايير (الالتزام بقواعد التعرج الصارمة في معيار ANSI يبدأ حول gcc-4، كما أشار توماس كامير).

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

بمعنى آخر، جرب هذا (لا أستطيع اختباره بنفسي الآن ولكن أعتقد أنه سينجح):

size_t t = *((size_t*)(char*)&f);

نصائح أخرى

في معيار C99، يتم تغطية ذلك من خلال القاعدة التالية في 6.5-7:

يجب أن يكون للكائن قيمته المخزنة التي يتم الوصول إليها إلا من خلال تعبير LVALUE الذي يحتوي على أحد الأنواع التالية: 73)

  • نوع متوافق مع النوع الفعال للكائن،

  • نسخة مؤهلة من نوع متوافق مع النوع الفعال للكائن،

  • نوع هو النوع الموقّع أو غير الموقّع المقابل للنوع الفعال للكائن ،

  • نوع هو النوع الموقّع أو غير الموقّع المقابل لإصدار مؤهل من النوع الفعال للكائن ،

  • نوع إجمالي أو اتحاد يتضمن أحد الأنواع المذكورة أعلاه بين أعضائها (بما في ذلك ، على نحو متكرر ، عضوًا في اتحاد فرعي أو محتوى) ، أو

  • نوع الحرف.

العنصر الأخير هو سبب عمل الإرسال أولاً إلى (char*).

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

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

size_t size = (size_t)(f);// هذا يعمل

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

في دول مجلس التعاون الخليجي يمكنك تعطيل هذا باستخدام مفتاح التحويل البرمجي.أعتقد -fno_strict_aliasing.

إنه رمز C سيء :-)

الجزء الإشكالي هو أنه يمكنك الوصول إلى كائن واحد من النوع float عن طريق تحويله إلى مؤشر عدد صحيح وإلغاء الإشارة إليه.

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

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

سيؤدي إلغاء الإشارة عبر مؤشر size_t في هذه الحالة إلى إرجاع بعض البيانات غير المهيأة من المكدس.

يمكنك القيام بأمرين للتغلب على هذا:

  1. استخدم اتحادًا مع عضو عائم وsize_t وقم بإجراء عملية الصب عبر كتابة الكلمات.ليست لطيفة ولكنها تعمل.

  2. استخدم memcopy لنسخ محتويات f إلى size_t الخاص بك.المترجم ذكي بما يكفي لاكتشاف هذه الحالة وتحسينها.

لماذا تعتقد أن t يجب أن يكون 0؟

أو بصيغة أكثر دقة، "لماذا تعتقد أن التمثيل الثنائي للنقطة العائمة صفر سيكون هو نفس التمثيل الثنائي للعدد الصحيح صفر؟"

هذا رمز C سيء.فريقك يكسر قواعد C المستعارة، والمُحسِّن مجاني للقيام بالأشياء التي تكسر هذا الرمز.من المحتمل أن تجد أن دول مجلس التعاون الخليجي قد قامت بجدولة قراءة size_t قبل كتابة الفاصلة العائمة (لإخفاء زمن انتقال خط الأنابيب fp).

يمكنك تعيين رمز التبديل -fno-strict-aliasing، أو استخدام Union أو reinterpret_cast لإعادة تفسير القيمة بطريقة متوافقة مع المعايير.

وبصرف النظر عن محاذاة المؤشر، فإنك تتوقع أن sizeof(size_t)==sizeof(float).لا أعتقد ذلك (في نظام التشغيل Linux 64 بت، يجب أن يكون size_t 64 بت ولكن يطفو 32 بت)، مما يعني أن الكود الخاص بك سيقرأ شيئًا غير مهيأ.

-O3 لا يعتبر "عاقل"، -O2 هو الحد الأعلى بشكل عام باستثناء بعض تطبيقات الوسائط المتعددة.

بعض التطبيقات لا يمكنها الوصول إلى هذا الحد، وتموت إذا تجاوزت -O1 .

إذا كان لديك دول مجلس التعاون الخليجي الجديدة بما فيه الكفاية (أنا على 4.3 هنا)، فقد يدعم هذا الأمر

  gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts

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

من man gcc :

  The output is sensitive to the effects of previous command line options, so for example it is possible to find out which
       optimizations are enabled at -O2 by using:

               -O2 --help=optimizers

       Alternatively you can discover which binary optimizations are enabled by -O3 by using:

               gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
               gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
               diff /tmp/O2-opts /tmp/O3-opts | grep enabled

لقد اختبرت الكود الخاص بك مع:"i686-apple-darwin9-gcc-4.0.1 (دول مجلس التعاون الخليجي) 4.0.1 (شركة أبل)بناء 5465)"

ولم تكن هناك مشكلة.انتاج:

t should be 0 but is 0

لذلك ليس هناك خطأ في التعليمات البرمجية الخاصة بك.هذا لا يعني أنه رمز جيد.لكنني سأضيف مجموعة العودة من الوظيفة الرئيسية و "العودة 0 ؛" في نهاية الوظيفة.

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