سؤال حول الاتحاد في متجر C كنوع واحد وقراءته كنوع آخر - هل تم تحديد التنفيذ؟

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

سؤال

كنت أقرأ عن union في C من K&R، بقدر ما فهمت، يمكن لمتغير واحد في union أن يحمل أيًا من الأنواع المتعددة وإذا تم تخزين شيء ما كنوع واحد واستخراجه كنوع آخر، فإن النتيجة هي تحديد التنفيذ البحت.

الآن يرجى التحقق من مقتطف الرمز هذا:

#include<stdio.h>

int main(void)
{
  union a
  {
     int i;
     char ch[2];
  };

  union a u;
  u.ch[0] = 3;
  u.ch[1] = 2;

  printf("%d %d %d\n", u.ch[0], u.ch[1], u.i);

  return 0;
}

انتاج:

3 2 515

أنا هنا أقوم بتعيين القيم في u.ch ولكن استرجاع من كليهما u.ch و u.i.هل تم تحديد التنفيذ؟أم أنني أفعل شيئًا سخيفًا حقًا؟

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

شكرًا.

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

المحلول

هذا سلوك غير محدد. u.i و u.ch تقع في نفس عنوان الذاكرة.لذا، فإن نتيجة الكتابة في أحدهما والقراءة من الآخر تعتمد على المترجم، والنظام الأساسي، والهندسة المعمارية، وأحيانًا حتى مستوى تحسين المترجم.وبالتالي فإن الإخراج ل u.i قد لا يكون دائما 515.

مثال

على سبيل المثال gcc على جهازي ينتج إجابتين مختلفتين ل -O0 و -O2.

  1. لأن جهازي يحتوي على بنية 32 بت صغيرة النهاية، مع -O0 انتهى بي الأمر مع وحدتي بايت أقل أهمية تمت تهيئتهما إلى 2 و 3، ولم تتم تهيئة البايتتين الأكثر أهمية.لذا تبدو ذاكرة الاتحاد كما يلي: {3, 2, garbage, garbage}

    ومن ثم أحصل على إخراج مشابه لـ 3 2 -1216937469.

  2. مع -O2, ، أحصل على إخراج 3 2 515 كما تفعل أنت، الأمر الذي يجعل ذاكرة الاتحاد {3, 2, 0, 0}.ما يحدث هو أن gcc تحسين المكالمة إلى printf بالقيم الفعلية، وبالتالي فإن مخرجات التجميع تبدو مكافئة لما يلي:

    #include <stdio.h>
    int main() {
        printf("%d %d %d\n", 3, 2, 515);
        return 0;
    }
    

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

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

نصائح أخرى

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

وقال أنت الذي كنت قراءة K & R. أحدث طبعة من هذا الكتاب (حتى الآن)، ويصف نسخة موحدة الأول من لغة C - C89 / 90. في هذا الإصدار من لغة C الكتابة أحد أعضاء النقابة والقراءة عضو آخر هو سلوك غير معرف . لا <م> التنفيذ تعريف (وهو شيء مختلف)، ولكن غير معروف السلوك. الجزء ذي الصلة من مستوى اللغة في هذه الحالة هو 6.5 / 7.

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

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

ولديك <م> محددة مثال على ذلك هو آمن نسبيا لنوع المجانسه من int إلى char[2] مجموعة. هو دائما القانوني في لغة C لإعادة تفسير محتوى أي كائن بمثابة مجموعة شار (مرة أخرى، 6.5 / 7).

ومع ذلك، فإن العكس ليس صحيحا. كتابة البيانات إلى عضو مجموعة char[2] من نقابتكم ثم قراءته باعتباره int يحتمل أن إنشاء تمثيل فخ وتؤدي إلى <م> سلوك غير معرف . وجود خطر محتمل حتى لو صفيف حرف لديه مدة كافية لتغطية int كامل.

ولكن في حالتك الخاصة، وإذا حدث int أن تكون أكبر من char[2]، فإن int تقرأ تغطي منطقة غير مهيأ بعد نهاية المصفوفة، الأمر الذي يؤدي مرة أخرى لسلوك غير معرف.

السبب وراء الإخراج هو أنه يتم تخزين الأعداد الصحيحة على جهازك ليتل إنديان شكل:يتم تخزين وحدات البايت الأقل أهمية أولاً.ومن هنا يمثل تسلسل البايت [3،2،0،0] عدد صحيح 3+2*256 = 515.

تعتمد هذه النتيجة على التنفيذ المحدد والنظام الأساسي.

والإخراج من هذه المدونة سوف تعتمد على النظام الأساسي الخاص بك وتنفيذ مترجم C. الإخراج الخاص بك يجعلني أعتقد أنك تقوم بتشغيل هذا الرمز على نظام ليت endian (وربما إلى x86). لو كنت لوضع 515 في ط وننظر في الأمر في مصحح، وكنت أرى أن البايت أدنى النظام سيكون 3، وسوف البايت التالي في الذاكرة تكون 2، التي تقوم بتعيين بالضبط ما كنت وضعت في الفصل.

إذا فعلت ذلك على نظام كبير-endian، سيكون لديك (على الأرجح) حصلت على 770 (على افتراض [إينتس] 16-بت) أو 50462720 (على افتراض [إينتس] 32-بت).

ومن تنفيذ المعالين وقد تختلف النتائج على منصة مختلفة / مترجم ولكن يبدو أن هذا هو ما يحدث:

515 في ثنائي هو

1000000011

والأصفار الحشو لجعله اثنين بايت (على افتراض كثافة العمليات 16 بت):

0000001000000011

وحدات البايت هما:

00000010 and 00000011

ما هو 2 و3

والأمل شخص يفسر لماذا يتم عكس أنهم - تخميني هو أن حرف لا عكس لكن كثافة العمليات هو endian القليل

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

عند تعيين ثلاثة (00000011) واثنين من (00000010) لمجموعة شار، حالة الاتحاد هي 0000001100000010. عندما تقرأ كثافة العمليات من هذا الاتحاد، فإنه يحول كل شيء إلى وصحيح. على افتراض تمثيل Endian طفيف حيث LSB يتم تخزينها عند أدنى عنوان، وقراءة كثافة العمليات من سيكون 0000001000000011 الاتحاد وهو ثنائي ل515.

ملحوظة: يحمل هذا صحيح حتى لو كان الباحث 32 بت - التحقق <لأ href = "https://stackoverflow.com/questions/1812348/a-question-about-union-in-c/1812376#1812376" > أمنون لإجابة

إذا كنت على نظام 32 بت، ثم عدد صحيح هو 4 بايت لكنك التهيئة فقط بايت فقط 2. الوصول إلى البيانات uninitialised هو السلوك غير معرف.

وعلى افتراض انك كنت على نظام مع [إينتس] 16-بت، ثم ما تقومون به لا يزال تنفيذ محددة. إذا كان النظام الخاص بك هو endian قليلا، ثم u.ch [0] سوف تتوافق مع البايت على الأقل كبيرا من واجهة المستخدم وu.ch <لأ href = "http://en.wikipedia.org/wiki/Two's_complement" يختلط = "نوفولو noreferrer"> 1 سيكون البايت الأكثر أهمية. على نظام endian كبيرة، انها على العكس من ذلك. أيضا، والمعيار C لا يجبر تنفيذ لاستخدام اثنين في تكملة لتمثيل صحيح وقعت القيم، على الرغم من متمم ثنائي هو الأكثر شيوعا. ومن الواضح أن حجم عدد صحيح أيضا تنفيذ محددة.

وتلميح: أنه من الأسهل لمعرفة ما يحدث إذا كنت تستخدم القيم الست عشرية. على نظام endian قليلا، فإن النتيجة في عرافة يكون 0x0203.

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