ما أفضل طريقة من هذه الطرق لحفظ لون 32Bit في لغة C؟

السلام عليكم ورحمة الله وبركاته.

من أول ما بدأتُ أهتم بهذه اللغة وأنا أجد ألف طريقة وطريقة للقيام بأي شيء، ولا أعرف أيهم الشائع والأفضل، على العموم إن كان لدي لونٌ RGBA مكونًا من 32Bit أي طريقة أفضل لحفظه؟ على أن تكون شائعة عند مبرمجي C، ويمكن الحصول على قيمة تلك الأرقام في قيمة uint32.

وجدتُ حوالي ست طرق:

  1. Union الطريقة المثلى لا عمليات إضافية، لكن فقط تمثيل الألوان غير قابل للقرأءة.

  2. Struct طريقة مفهومة وواضحة، فقط مشكلة عمل الـCast عند الحصول عليها كقيمة uint32.

  3. Union بداخله struct طريقة رائعة لتمثيل الألوان، لكن ما مدى شيوعها وهل هي موافقة لأفضل الممارسات الـC؟

4 و5: مؤشر وحجز ذاكرة باستخدام malloc الفرق بين 5 و4 أن الأولى تمثل المؤشرات باستخدام مصفوفة والثانية بالمؤشر، قرأتُ أكثر من مرة أن المؤشرات تكون أسرع من المصفوفات في أكثر المجمعات، رغم أن الطريقتين غبيتان، إلا أني قرأتُ أكثر من مرة شخص يستخدمهما في C/C++.

6: حجز رقم والقيام بعملات Bitwise عليه، لا أعرف مدة سرعة هذه الطريقة، أراها مترددة بكثر في C وغيرها،

هذه شيفرة محاولة تطبيق مني، إن كان هنالك أي ملاحظات عليها:

#include <stdint.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>

typedef union {
    unsigned char C[4];
    uint32_t color;
} COLOR_T;

typedef struct {
    unsigned char R;
    unsigned char G;
    unsigned char B;
    unsigned char A; 
} COLORS_T;

typedef union {
    COLORS_T C;
    uint32_t color;
} COLORUS_T;

int main() {
    // الطريقة الأولى
    COLOR_T *ucolor = malloc(sizeof (COLOR_T*));

    ucolor->C[0] = 57; // r
    ucolor->C[1] = 200; // g
    ucolor->C[2] = 182; // b
    ucolor->C[3] = 14; // a
    printf("rgba(%u, %u, %u, %u) COLOR=%"PRIu32"\n",
        ucolor->C[0],
        ucolor->C[1],
        ucolor->C[2],
        ucolor->C[3], ucolor->color);
    free(ucolor);

    // الطريقة الثانية        
    COLORS_T *scolor = malloc(sizeof (COLORS_T*));

    scolor->R = 57;
    scolor->G = 200;
    scolor->B = 182;
    scolor->A = 14;

    printf("rgba(%u, %u, %u, %u) COLOR=%"PRIu32"\n",
        scolor->R,
        scolor->G,
        scolor->B,
        scolor->A, *(uint32_t*)(scolor));
    free(scolor);

    // الطريقة الثالثة
    COLORUS_T *sucolor = malloc(sizeof (COLORUS_T*));

    sucolor->C.R = 57;
    sucolor->C.G = 200;
    sucolor->C.B = 182;
    sucolor->C.A = 14;

    printf("rgba(%u, %u, %u, %u) COLOR=%"PRIu32"\n",
        sucolor->C.R,
        sucolor->C.G,
        sucolor->C.B,
        sucolor->C.A, sucolor->color);
    free(sucolor);

    // الطريقة الرابعة
    unsigned char *pcolor = malloc(sizeof (char*) * 4);

    pcolor[0] = 57;
    pcolor[1] = 200;
    pcolor[2] = 182;
    pcolor[3] = 14;

    printf("rgba(%u, %u, %u, %u) COLOR=%"PRIu32"\n",
        pcolor[0],
        pcolor[1],
        pcolor[2],
        pcolor[3], *(uint32_t*)(pcolor));
    free(pcolor);

    // الطريقة الخامسة
    unsigned char *pcolor2 = malloc(sizeof (char*) * 4);

    *pcolor2 = 57;
    *(pcolor2+1) = 200;
    *(pcolor2+2) = 182;
    *(pcolor2+3) = 14;

    printf("rgba(%u, %u, %u, %u) COLOR=%"PRIu32"\n",
        *pcolor,
        *(pcolor+1),
        *(pcolor2+2),
        *(pcolor2+3), *(uint32_t*)(pcolor2));
    free(pcolor2);

    // الطريقة السادسة
    uint32_t uintcolor = 0;

    uintcolor |= 57; // r
    uintcolor |= (200 << 8); // g
    uintcolor |= (182 << 16); // b
    uintcolor |= (14 << 24); // a

    printf("rgba(%u, %u, %u, %u) COLOR=%"PRIu32"\n",
        uintcolor & 255,
        uintcolor >> 8 & 255,
        uintcolor >> 16 & 255,
        uintcolor >> 24 & 255, uintcolor);
}
يرجى الدخول لحسابك أو تسجيل حساب لتستطيع إضافة تعليق
حساب جديد دخول

التعليقات

a3f أضف ردا
extern int *p;
extern int a[]; 

int main(void) {
    for (int i = 0; i < 4; i++) {
        printf("%d %d\n, p[i], a[i]);
    }
}

في حالة المؤشر، الكومبيلر يجب أن يأخذ في الاعتبار أن قيمة p تتغير ويجب عليه أن يحمل قيمة p من الذاكرة ثم يdereference اياها في حالة الـarray فالعنوان لا يتغير وبالتالي يمكن تخزين العنوان في الكود مباشرة ويتم توفير dereference.

في الرابعة والخامسة عندك لا يوجد arrays، مؤشرات فقط مع الفرق في الـSyntax. سيولدان نفس الكود تماما. حتى لو إن هناك فرق في الأداء لم تظن أن الكمبيلر لا يستطيع التحويل بينهم كما فعلت أنت؟ طريقة الـstructs ستولد نفس الكود كذلك. فقط الطريقة الأخيرة ستولد كود مختلف. ليس فقط من حيث الأوامر (سيستخدم ORs بدلا من MOVs) ولكن من حيث الوظيفة. سواء إستخدمت struct أو array فعنوان r في الذاكرة يسبق عنوان g. المعيار يشترط ذلك على الكمبيلر. أما لو إستخدم bitshift فأول 8 bit سيكونون في عنوان أصغر على معماريات little endian وفي العنوان الأكبر على big endian.

أنا أستخدم union مع anonymous struct بداخله.

union color {
struct rgba {
    uint8_t r;
    uint8_t g;
    uint8_t b;
    uint8_t a;
};
    uint32_t uint;
};
_Static_assert(sizeof (union color) == sizeof (uint32_t), "There's padding");

تعليقات:

  • الـstruct ربما يحوي padding على معمارية ما، لذلك ال ‎._Static_assert

  • هذه الطريقة غير معرفة السلوك في C++‎ (لا يجوز هناك قراءة union member مختلف عن الذي تمت الكتابة فيه لكن كمبيلرات كتيرة تدعم ذلك)

  • إقرأ الـassembly الناتج عند ‎-O2 قبل الافتاء بشأن السرعة

  • أنت قمت بنفس الخطأ الذي فعلته في كود الـGo السابق: يجب أن تحجز حجم ال struct وليس حجم ال pointer عليه!

  • لا يجوز أن تعمل cast من ‎&structure ل uint32_t*‎ ثم تdereference. معالجات Intel لا تبالي لكنه سلوك غير معرف ربما يعطيك bus error على ARM. عامة ابعد عن أي cast لل pointers ما دمت لا تعلم إن كان مسموحا.

يوسف سيد أضف ردا

شكرًا لك. على وقتك، وعلى مجهودك معي :)

الـstruct ربما يحوي padding على معمارية ما، لذلك ال ‎._Static_assert

سأستخدمها دائمًا -بإذن الله-، لكن فقط لما تضيف معمارية padding :)؟

إقرأ الـassembly الناتج عند ‎-O2 قبل الافتاء بشأن السرعة

:) في مسألة المصفوفة والمؤشر وجدتُ إختلافًا بسيطًا كما يقول الكتاب الذي كنت أقرأئه بشأن هذا لهذا ظننتُ أن هنالك اختلافًا، لن أفتي مجددًا :)

أنت قمت بنفس الخطأ الذي فعلته في كود الـGo السابق: يجب أن تحجز حجم ال struct وليس حجم ال pointer عليه!

أه لهذا يقولون malloc تحجز مساحة في الذاكرة وترجع مؤشرات لها، بشيفرتي سيكون الحجم ثابتًا دائمًا :)، شكرًا لك،

لا يجوز أن تعمل cast من ‎&structure ل uint32_t*‎ ثم تdereference. معالجات Intel لا تبالي لكنه سلوك غير معرف ربما يعطيك bus error على ARM. عامة ابعد عن أي cast لل pointers ما دمت لا تعلم إن كان مسموحا.

إلى الآن لم أستعب هذا، لكني فهمتُ أن لا أقوم بـcast لمؤشر، كذا قرأتُ هذه المقالة:

سأحول فهمُ هذا الأمر شكرًا لك،

لو كان هنالك مصدرًا حاسمًا يوقف الحماقة التي أكتبها سأكون شاكرًا لك :)

تعديل وجدت هذا سأحول قراءة ما أستطيع من هذه الكتب بالترتيب:

a3f أضف ردا

نعم عندما يكون لديك array عمره auto و pointer يشير إلى مساحة محجوزة فبطبيعة الحال الكود مختلف لأنهما شيئان مختلفان.

أما ما فعلته أنت هو مجرد تغيير syntax الـindexing ([] بدلا من * والجمع) وهذا ليس له أي تأثير على الكود المولد لأنهما نفس الشيء.

الـpadding يكون لظروف تقنية. مثلا المعالج لا يدعم قراءة 4 بايت إلا من عنوان مضاعف للـ4 (أي باقي قسمته على 4 هو الصفر). يسمى ذلك alignment. لا أعرف أي معماريات دارجة سترى جدوى في إضافة padding في هذا ال struct تحديدا (لأنها بايتات فحسب) لكن ‎_Static_assert هنا تحسم الأمر وتجعل سلوك الكود واضحا على كل المعماريات: إما أن يعمل كما صممته أو يرفض أن يـcompile من الأساس.

cast المؤشرات عموما يجب أن تقتصره على ما ذكر في معيار C11 المادة Expressions 6.5 الفقرة 7 (من هنا مثلا:

السبب في ذلك من ناحية هو أن نوع المؤشر لا يخبر الكومبيلر بالحجم فحسب ولكن بالـalignment كذلك. أما الـstruct فمكون من 4 بايت منفصلة ولا شيء يرغم الكمبيلر على أن يضعه عند عنوان يقبل القسمة على 4. هذا الـ unaligned access سيتسبب في bus error على الغالبية العظمى من المعماريات. القليل منها مثل Intel x86 تصلح من الموضوع ولا تعاني إلا تدنيا في الأداء في حالات معينة (عند عبور cache line تحديدا) ولكن كما قلت هو سلوك شاذ من الخطأ أن تعتمد عليه.

السبب الأخر هو الـstrict aliasing وهذا يطول الحديث فيه. أنتوي كتابة مقال عنه في وقت ما. هو باختصار يسمح للكمبيلر بأن يتجاهل تماما إحتمالية إن يتم إستخدام مؤشرين غير متكافئين (مثل int*‎ و struct rgb*‎) للإشارة إلى نفس الشيء حتى لو أن الـalignment يسمح بذلك. الـstrict aliasing لازم لكي يكون لكمبيلرات السي أمل في أن تولد كود يستطيع منافسة كمبيلرات لغات مثل FORTRAN لا تعاني من وجود المؤشرات.