سؤال

خلال مراجعة التعليمات البرمجية، صادفت بعض التعليمات البرمجية التي تحدد بنية بسيطة على النحو التالي:

class foo {
   unsigned char a;
   unsigned char b;
   unsigned char c;
}

في مكان آخر، يتم تعريف مجموعة من هذه الكائنات:

foo listOfFoos[SOME_NUM];

في وقت لاحق، يتم نسخ الهياكل الخام في المخزن المؤقت:

memcpy(pBuff,listOfFoos,3*SOME_NUM);

يعتمد هذا الرمز على الافتراضات التي: أ.) حجم FOO هو 3، ولا يتم تطبيق أي حشوة، و B.) مجموعة من هذه الكائنات معبأة بدون حشوة بينهما.

لقد جربتها مع جنو على نظامين (Redhat 64b، Solaris 9)، وعملت على حد سواء.

هي الافتراضات أعلاه صالحة؟ إذا لم يكن الأمر كذلك، في ظل أي ظروف (مثل التغيير في نظام التشغيل / التحويل البرمجي) قد تفشل؟

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

المحلول

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

نظرا لأنك تعمل مع Char's، فإن الافتراضات ربما تكون في كثير من الأحيان أكثر من غير لا، ولكن معيار C ++ لا يضمن ذلك بالتأكيد. يمكن أن يؤدي مترجم مختلف، أو حتى مجرد تغيير في الأعلام التي تم تمريرها إلى برنامج التحويل البرمجي الحالي إلى إدراج الحشو بين عناصر الهيكل أو بعد العنصر الأخير من الهيكل، أو كليهما.

نصائح أخرى

سيكون بالتأكيد أكثر أمانا للقيام به:

sizeof(foo) * SOME_NUM

إذا قمت بنسخ صفيفك مثل هذا يجب عليك استخدامه

memcpy(pBuff,listOfFoos,sizeof(listOfFoos));

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

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

أعتقد أن السبب في أن هذا يعمل لأن جميع الحقول الموجودة في الهيكل تشار أي محاذاة واحدة. إذا كان هناك حقل واحد على الأقل لا يمنع 1، فلن يكون محاذاة الهيكل / الفصل 1 (سيعتمد المحاذاة على النظام الميداني والمحاذاة).

دعونا نرى بعض المثال:

#include <stdio.h>
#include <stddef.h>

typedef struct {
    unsigned char a;
    unsigned char b;
    unsigned char c;
} Foo;
typedef struct {
    قصيرة غير موقعة
    unsigned char  a;
    unsigned char  b;
    unsigned char  c;
} Bar;
typedef struct { Foo F[5]; } F_B;
typedef struct { Bar B[5]; } B_F;


#define ALIGNMENT_OF(t) offsetof( struct { char x; t test; }, test )

int main(void) {
    printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo));
    printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar));
    printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B));
    printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F));
}

عند تنفيذها، والنتيجة هي:

Foo:: Size: 3; Alignment: 1
Bar:: Size: 6; Alignment: 2
F_B:: Size: 15; Alignment: 1
B_F:: Size: 30; Alignment: 2

يمكنك أن ترى هذا البار و F_B لديه محاذاة 2 بحيث يكون حقلها محاذاة بشكل صحيح. يمكنك أيضا أن ترى حجم الشريط هو 6 وليس 5. وبعد وبالمثل، حجم B_F (5 من الشريط) هو 30 وليس 25.

لذلك، إذا كنت شفرة صعبة بدلا من sizeof(...), ، سوف تحصل على مشكلة هنا.

أتمنى أن يساعدك هذا.

كل شيء يأتي إلى محاذاة الذاكرة. آلات 32 بت نموذجية تقرأ أو تكتب 4 بايت من الذاكرة لكل محاولة. هذا الهيكل آمن من المشاكل لأنه يقع تحت 4 بايت بسهولة مع عدم وجود مشاكل حشو مربكة.

الآن إذا كان الهيكل على هذا النحو:

class foo {
   unsigned char a;
   unsigned char b;
   unsigned char c;
   unsigned int i;
   unsigned int j;
}

منطق زملاء العمل الخاص بك ربما يؤدي إلى

memcpy(pBuff,listOfFoos,11*SOME_NUM);

(3 char's = 3 bytes، 2 ints = 2 * 4 bytes، لذلك 3 + 8)

لسوء الحظ، بسبب حشوة الهيكل يأخذ فعلا 12 بايت. هذا لأنك لا تستطيع أن تناسب ثلاثة من Char's و int في تلك الكلمة 4 البايت، وبالتالي هناك بايت واحد من المساحة المبطنة هناك تدفع Int int في كلمتها الخاصة. يصبح هذا أكثر وأكثر من مشكلة كلما أصبحت أنواع البيانات أكثر تنوعا.

بالنسبة للحالات التي يتم فيها استخدام الأشياء مثل هذا، ولا يمكنني تجنبها، أحاول أن أجعل استراحة التجميع عندما لا تعقد الفوضى. يمكنني استخدام شيء مثل ما يلي (أو boost.statationalert. إذا كان الوضع يسمح):

static_assert(sizeof(foo) <= 3);

// Macro for "static-assert" (only usefull on compile-time constant expressions)
#define static_assert(exp)           static_assert_II(exp, __LINE__)
// Macro used by static_assert macro (don't use directly)
#define static_assert_II(exp, line)  static_assert_III(exp, line)
// Macro used by static_assert macro (don't use directly)
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)}

كنت آمن واستبدل الرقم السحري 3 مع sizeof(foo) أنا أحسب.

تخميني هو أن الشفرة الأمثل لبنية المعالجات المستقبلية قد تقدم من المحتمل أن تقدم شكل الحشو.

ومحاولة تعقب هذا النوع من الخطأ هو ألم حقيقي!

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

لمنصة سائدة، ربما تكون بخير، لكنها ليست ضمانة.

قد لا تزال هناك مشكلة مع Sizeof () عند تمرير البيانات بين جهازي كمبيوتر. على أحدهم قد يترجم التعليمات البرمجية مع الحشوة وفي الآخر دون، في هذه الحالة Sizeof () من شأنه أن يعطي نتائج مختلفة. إذا تم تمرير بيانات الصفيف من كمبيوتر إلى آخر، فسيتم تسيء تفسير تفسيرها لأن عناصر الصفيف لن يتم العثور على المكان المتوقع. أحد الحلول هو التأكد من استخدام #Pragma Pack (1) كلما أمكن ذلك، ولكن قد لا يكون ذلك كافيا للمصفوفات. الأفضل هو التوقع على المشكلة واستخدام الحشو إلى مضاعفات 8 بايت لكل عنصر صفيف.

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