C/C++ لماذا استخدام char غير موقعة للبيانات الثنائية؟



character-encoding bytebuffer (7)

يتم تعريف التنفيذ الموقّع من النوع char العادي ، لذلك ما لم تكن تتعامل فعليًا مع بيانات الأحرف (سلسلة تستخدم مجموعة أحرف النظام الأساسي - عادةً ASCII) ، من الأفضل عادة تحديد الموقعة صراحة باستخدام إما signed char أو signed char unsigned char .

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

هل من الضروري حقًا استخدام unsigned char للاحتفاظ بالبيانات الثنائية كما في بعض المكتبات التي تعمل على ترميز الأحرف أو المخازن المؤقتة الثنائية؟ لفهم سؤالي ، ألق نظرة على الكود أدناه -

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

كلا مخرجات printf's 𤭢 صحيح ، حيث f0 a4 ad a2 هو تشفير Unicode-point U+24B62 (𤭢) في ست عشري.

حتى memcpy نسخ بشكل صحيح البتات التي عقدها شار.

ما المنطق الذي يمكن أن يدافع عن استخدام unsigned char بدلاً من plain char ؟

في الأسئلة الأخرى ذات الصلة ، يتم تمييز unsigned char غير الموقّع لأنه نوع البيانات الوحيد (البايت / الأصغر) الذي يضمن عدم وجود حشو حسب مواصفات C. ولكن كما هو موضح أعلاه ، لا يبدو أن الإخراج يتأثر بأي حشوة على هذا النحو.

لقد استخدمت VC ++ Express 2010 و MinGW لتجميع ما ورد أعلاه. على الرغم من VC أعطى التحذير

warning C4309: '=' : truncation of constant value

لا يبدو الإخراج يعكس ذلك.

سكرتير خاص هذا يمكن أن يكون علامة مكررة محتملة من يجب أن يتم توقيع مخزن مؤقت للبايت أو العازلة شار غير الموقعة؟ لكن نيتي مختلفة. أنا أسأل لماذا يجب أن تتم كتابة شيء يبدو أنه يعمل بشكل جيد مع unsigned char ؟

تحديث: للاقتباس من N3337 ،

Section 3.9 Types

2 بالنسبة لأي كائن (بخلاف موضوع فرعي من الفئة الأساسية) من النوع T القابل للنسخ بسهولة ، سواء كان الكائن يحمل قيمة صالحة من النوع T أم لا ، يمكن نسخ البايتات الأساسية (1.7) التي تشكل الكائن إلى صفيف من الأحرف char أو شار غير موقعة. إذا تم نسخ محتوى صفيف char أو char غير الموقَّع إلى الكائن ، فسيحتفظ الكائن بعد ذلك بقيمته الأصلية.

نظرًا للحقيقة الموضحة أعلاه وأن المثال الأصلي الخاص بي كان على جهاز Intel حيث يوجد char افتراضيًا على signed char ، ما زلت غير مقتنع بما إذا كان يجب أن يكون unsigned char مفضل على char .

أي شيء آخر؟


Answer #1

في C ، يكون نوع بيانات unsigned char هو نوع البيانات الوحيد الذي يحتوي على جميع الخصائص الثلاثة التالية في وقت واحد

  • لا تحتوي على وحدات بت حشو ، حيث تسهم كل وحدات تخزين التخزين في قيمة البيانات
  • لا توجد عملية bitwise تبدأ من قيمة من هذا النوع ، عند تحويلها مرة أخرى إلى هذا النوع ، يمكن أن تنتج فيض ، تمثيل اعتراض أو سلوك غير محدد
  • قد يكون اسمًا مستعارًا لأنواع البيانات الأخرى دون انتهاك "قواعد الاسم المستعار" ، أي أنه سيتم ضمان الوصول إلى نفس البيانات من خلال مؤشر يتم كتابته بشكل مختلف لرؤية جميع التعديلات

إذا كانت هذه هي خصائص نوع البيانات "الثنائية" الذي تبحث عنه ، فينبغي عليك استخدام unsigned char نهائيًا.

للخاصية الثانية نحتاج إلى نوع unsigned . لكل هذه التحويلات يتم تعريفها باستخدام modulo arihmetic ، هنا modulo UCHAR_MAX+1 ، 256 في معظم 99٪ من البنى. كل تحويل القيم الأوسع إلى unsigned char يتوافق مع اقتطاع البايت الأقل أهمية.

لا يعمل نوعي الأحرف الآخرين بشكل عام. signed char موقعة ، على أي حال ، لذا فإن تحويل القيم التي لا تتناسب مع ذلك لم يتم تعريفه جيدًا. char غير ثابت لتوقيعه أو عدم توقيعه ، ولكن على نظام أساسي معين تم نقل الكود إليه ، فقد يتم توقيعه حتى أنه غير موقّع على نظامك.


Answer #2

نوع char عادي هو مشكلة ويجب عدم استخدامها لأي شيء سوى سلاسل. المشكلة الرئيسية في char هي أنه لا يمكنك معرفة ما إذا كانت موقعة أو غير موقعة: هذا هو السلوك المحدد بالتنفيذ. هذا يجعل char مختلفًا عن int وما إلى ذلك ، ويضمن دائمًا توقيع int .

على الرغم من أن VC أعطى التحذير ... اقتطاع القيمة الثابتة

إنها تخبرك أنك تحاول تخزين الحرفيات int داخل متغيرات char. قد يكون هذا مرتبطًا بالتوقيع: إذا حاولت تخزين عدد صحيح بقيمة> 0x7F داخل شخصية موقعة ، فقد تحدث أشياء غير متوقعة. بشكل رسمي ، هذا سلوك غير محدد في C ، على الرغم من أنه من الناحية العملية ستحصل فقط على ناتج غريب عند محاولة طباعة النتيجة كقيمة عددية مخزنة داخل حرف (موقّع).

في هذه الحالة المحددة ، يجب ألا يكون التحذير مهمًا.

تصحيح :

في الأسئلة الأخرى ذات الصلة ، يتم تمييز الحرف غير الموقّع لأنه نوع البيانات الوحيد (البايت / الأصغر) الذي يضمن عدم وجود حشو حسب مواصفات C.

من الناحية النظرية ، يُسمح لجميع أنواع الأعداد الصحيحة باستثناء char غير الموقعة و char الموقعة أن تحتوي على "وحدات حشوة" ، وفقًا للمواصفة C11 6.2.6.2:

"بالنسبة لأنواع الأعداد الصحيحة غير الموقعة بخلاف char غير الموقعة ، تقسم بتات تمثيل الكائن إلى مجموعتين: بتات القيمة وبتات الحشو (لا يلزم وجود أي من الأخير)."

"بالنسبة لأنواع الأعداد الصحيحة الموقعة ، تقسم بتات تمثيل الكائن إلى ثلاث مجموعات: بتات القيمة ، بتات الحشو ، وبتة الإشارة. لا يلزم أن يكون هناك أي بتات حشو ؛ لا يجب أن تحتوي حروف توقيع موقعة على أي بتات حشوة."

المعيار C غامض وغامض عمداً ، مما يسمح بتات الحشو النظرية هذه للأسباب التالية:

  • لأنها تتيح جداول الرموز المختلفة من تلك 8 بت القياسية.
  • يسمح بالتوقيع المعرف بالتنفيذ وتنسيقات عدد صحيح موقعة غريبة مثل تكملة المرء أو "علامة وحجم".
  • قد لا يستخدم عدد صحيح بالضرورة جميع البتات المخصصة.

ومع ذلك ، في العالم الحقيقي خارج معيار C ، ينطبق ما يلي:

  • من شبه المؤكد أن جداول الرموز هي 8 بتات (UTF8 أو ASCII). توجد بعض الاستثناءات الغريبة ، لكن التطبيقات النظيفة تستخدم النوع القياسي wchar_t عند تطبيق جداول الرموز الأكبر من 8 بتات.
  • الدلالة هي دائما مكمّلة للاثنين.
  • يستخدم عدد صحيح دائمًا كل البتات المخصصة.

لذلك ليس هناك سبب حقيقي لاستخدام char غير الموقعة أو char الموقّعة لتفادي بعض السيناريوهات النظرية في المعيار C.


Answer #3

عادة ما تكون البايتات مخصصة للأعداد الصحيحة 8 بت غير الموقعة.

الآن ، لا تحدد char علامة الأعداد الصحيحة: في بعض المترجمات ، يمكن توقيع char ، وفي البعض الآخر ، قد تكون غير موقعة.

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

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';
c[0] >>= 1; // If char is signed, will the 7th bit go to 0 or stay the same?

bool isBiggerThan0 = c[0] > 0; // FALSE if char is signed!

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);

فيما يتعلق بالتحذير أثناء التحويل البرمجي: إذا تم توقيع char فأنت تحاول تعيين القيمة 0xf0 ، والتي لا يمكن تمثيلها في char الموقعة (النطاق من 128 إلى +127) ، لذلك سيتم توجيهها إلى قيمة موقعة (- 16).

سيؤدي الإعلان عن علامة char إلى التوقيع على إزالة التحذير ، ومن الجيد دائمًا أن يكون لديك بنية نظيفة دون أي تحذير.


Answer #4

ستحصل على معظم مشكلاتك عند مقارنة محتويات وحدات البايت الفردية:

char c[5];
c[0] = 0xff;
/*blah blah*/
if (c[0] == 0xff)
{
    printf("good\n");
}
else
{
    printf("bad\n");
}

يمكن طباعة "سيئة" ، لأنه وفقًا للمترجم الخاص بك ، سيتم تمديد علامة c [0] إلى -1 ، وهي ليست بنفس طريقة 0xff


Answer #5

أنا أسأل لماذا يجب أن تتم كتابة شيء يبدو أنه يعمل بشكل جيد مع char؟

إذا قمت بأشياء ليست "صحيحة" بمعنى المعيار ، فأنت تعتمد على سلوك غير محدد. قد يقوم المترجم الخاص بك بذلك بالطريقة التي تريدها اليوم ، لكنك لا تعرف ما الذي تفعله غدًا. لا تعرف ما الذي يفعله GCC أو VC ++ 2012. أو حتى إذا كان السلوك يعتمد على عوامل خارجية أو مجموعات Debug / Release وما إلى ذلك بمجرد مغادرة المسار الآمن للمعيار ، فقد تواجه مشكلة.


Answer #6

حسنًا ، ماذا تسمي "البيانات الثنائية"؟ هذه مجموعة من البتات ، دون أي معنى معين لها بواسطة هذا الجزء المحدد من البرنامج الذي يطلق عليها "البيانات الثنائية". ما هو أقرب نوع البيانات البدائية ، والذي ينقل فكرة عدم وجود أي معنى محدد لأي واحد من هذه البتات؟ أعتقد unsigned char .





rawbytestring