أسوأ الآثار الجانبية من chars التوقيع. (شرح آثار التوقيع على chars والقبث)
-
25-09-2019 - |
سؤال
أعمل بشكل متكرر مع المكتبات التي تستخدم char عند العمل مع Bytes في C ++. البديل هو تحديد "بايت" على أنه شار غير موقّع ولكن ليس المعيار الذي قرروا استخدامه. كثيرًا ما أقوم بتمرير البايتات من C# إلى C ++ DLLs وألقيها على شار للعمل مع المكتبة.
عند إلقاء ints إلى chars أو chars إلى أنواع بسيطة أخرى ، ما هي بعض الآثار الجانبية التي يمكن أن تحدث. على وجه التحديد ، متى كان هذا الرمز المكسور الذي عملت عليه وكيف اكتشفت أنه كان بسبب توقيع char؟
محظوظ أنني لم أواجه هذا في الكود الخاص بي ، واستخدمت خدعة تم توقيع char مرة أخرى في فصل أنظمة مضمنة في المدرسة. أتطلع إلى فهم المشكلة بشكل أفضل لأنني أشعر أنها ذات صلة بالعمل الذي أقوم به.
المحلول
أحد المخاطر الرئيسية هو إذا كنت بحاجة إلى تغيير البايتات. يحتفظ Char الموقّع بالتوقيع عند التحول الأيمن ، في حين أن char غير موقعة لا. هذا برنامج اختبار صغير:
#include <stdio.h>
int main (void)
{
signed char a = -1;
unsigned char b = 255;
printf("%d\n%d\n", a >> 1, b >> 1);
return 0;
}
يجب أن تطبع -1 و 127 ، على الرغم من أن A و B يبدأان بنفس نمط البت (المعطى من 8 بتات ، والكمالين والقيم الموقعة باستخدام التحول الحسابي).
باختصار ، لا يمكنك الاعتماد على Shift Working بشكل متطابق مع chars الموقعة وغير الموقعة ، لذلك إذا كنت بحاجة إلى قابلية النقل ، فاستخدم unsigned char
عوضا عن char
أو signed char
.
نصائح أخرى
تأتي أكثر أنواع القولات وضوحًا عندما تحتاج إلى مقارنة القيمة الرقمية لـ a char
مع ثابت سداسي عشري عند تنفيذ بروتوكولات أو ترميز مخططات.
على سبيل المثال ، عند تطبيق Telnet ، قد ترغب في القيام بذلك.
// Check for IAC (hex FF) byte
if (ch == 0xFF)
{
// ...
أو عند اختبار تسلسل UTF-8 متعدد البايت.
if (ch >= 0x80)
{
// ...
لحسن الحظ ، هذه الأخطاء لا تبقى عادةً ما لا تنجو حتى الاختبارات الأكثر شمولية على منصة مع توقيعها char
يجب أن تكشفهم. يمكن إصلاحها باستخدام ثابت حرف ، وتحويل الثابت الرقمي إلى أ char
أو تحويل الحرف إلى unsigned char
قبل أن يروج مشغل المقارنة على حد سواء إلى int
. تحويل char
مباشرة إلى unsigned
لن تعمل ، رغم ذلك.
if (ch == '\xff') // OK
if ((unsigned char)ch == 0xff) // OK, so long as char has 8-bits
if (ch == (char)0xff) // Usually OK, relies on implementation defined behaviour
if ((unsigned)ch == 0xff) // still wrong
لقد تعرضت للعض من قبل char signedness في كتابة خوارزميات البحث التي استخدمت الأحرف من النص كمؤشرات في أشجار الحالة. لقد واجهت أيضًا مشاكل عند توسيع الشخصيات في أنواع أكبر ، وينتشر البت المسبق للمشاكل في مكان آخر.
اكتشفت عندما بدأت في الحصول على نتائج غريبة ، و Segfaults الناشئة عن البحث عن نصوص أخرى غير تلك التي استخدمتها أثناء التطوير الأولي (من الواضح أن الشخصيات ذات القيم> 127 أو <0 ستتسبب في ذلك ، ولن تكون بالضرورة موجود في ملفاتك النصية النموذجية.
تحقق دائمًا من توقيع المتغير عند العمل معه. بشكل عام ، أقوم الآن بعمل أنواع موقعة ما لم يكن لدي سبب وجيه خلاف ذلك ، والكسب عند الضرورة. هذا يناسب بشكل جيد مع الاستخدام في كل مكان char
في المكتبات لتمثيل بايت. ضع في اعتبارك أن توقيع char
لم يتم تعريفه (على عكس الأنواع الأخرى) ، يجب أن تعطيه علاجًا خاصًا ، وأن تكون مدركًا.
الشخص الذي يزعجني أكثر:
typedef char byte;
byte b = 12;
cout << b << endl;
تأكد من أنها مستحضرات التجميل ، لكن arrr ...
عند إلقاء ints إلى chars أو chars إلى أنواع بسيطة أخرى
النقطة الحرجة هي أن إلقاء قيمة موقعة من نوع بدائي إلى نوع آخر (أكبر) لا يحتفظ بنمط البتات (على افتراض تكملة اثنين). شار موقّع بنمط بت 0xff
هو -1 ، في حين أن التوقيع قصير مع القيمة العشرية -1 0xffff
. إلقاء شار غير موقعة مع القيمة 0xff
إلى قصير غير موقعة ، ومع ذلك ، العائدات 0x00ff
. لذلك ، فكر دائمًا في التوقيع المناسب قبل أن تقوم بالتكوين إلى نوع بيانات أكبر أو أصغر. لا تحمل أبدًا بيانات غير موقعة في أنواع البيانات الموقعة إذا لم تكن بحاجة إلى ذلك - إذا كانت المكتبة الخارجية تفرض عليك القيام بذلك ، فقم بالتحويل في وقت متأخر قدر الإمكان (أو في أقرب وقت ممكن إذا كان الرمز الخارجي يعمل كمصدر للبيانات).
تحدد مواصفات لغة C و C ++ 3 أنواع بيانات لعقد الأحرف: char
, signed char
و unsigned char
. تمت مناقشة الأخير 2 في إجابات أخرى. دعونا نلقي نظرة على char
يكتب.
المعيار (المعيار) يقول أن char
نوع البيانات مايو يتم توقيعها أو غير موقّع وهو قرار تنفيذ. هذا يعني أن بعض المترجمين أو إصدارات المترجمين ، يمكنهم تنفيذها char
بشكل مختلف. المعنى الضمني هو أن char
نوع البيانات لا يفضي إلى العمليات الحسابية أو المنطقية. للعمليات الحسابية والطغل ، signed
و unsigned
إصدارات char
سوف تعمل بشكل جيد.
باختصار ، هناك 3 إصدارات من char
نوع البيانات. ال char
يعمل نوع البيانات جيدًا لعقد الشخصيات ، ولكنه غير مناسب للحساب عبر المنصات والمترجمين لأنه التوقيع يتم تعريف التنفيذ.
سوف تفشل فشلاً ذريعًا عند التجميع لمنصات متعددة لأن معيار C ++ لا يحدد char
أن تكون من "توقيع" معين.
لذلك تقدم مجلس التعاون الخليجي -fsigned-char
و -funsigned-char
خيارات لإجبار بعض السلوك. يمكن العثور على المزيد حول هذا الموضوع هنا, ، فمثلا.
تعديل:
كما طلبت أمثلة من الكود المكسور ، هناك الكثير من الاحتمالات لكسر التعليمات البرمجية التي تعالج البيانات الثنائية. على سبيل المثال ، تقوم الصورة بمعالجة عينات الصوت المكونة من 8 بت (النطاق -128 إلى 127) وتريد أن تهدأ مستوى الصوت. تخيل الآن هذا السيناريو (الذي يفترض فيه المبرمج الساذج char == signed char
):
char sampleIn;
// If the sample is -1 (= almost silent), and the compiler treats char as unsigned,
// then the value of 'sampleIn' will be 255
read_one_byte_sample(&sampleIn);
// Ok, halven the volume. The value will be 127!
char sampleOut = sampleOut / 2;
// And write the processed sample to the output file, for example.
// (unsigned char)127 has the exact same bit pattern as (signed char)127,
// so this will write a sample with the loudest volume!!
write_one_byte_sample_to_output_file(&sampleOut);
أتمنى أن تعجبك هذا المثال ؛-) ولكن بصراحة ، لم أجد مثل هذه المشكلات أبدًا ، ولا حتى كمبتدئ بقدر ما أتذكر ...
آمل أن تكون هذه الإجابة كافية لك. ماذا عن تعليق قصير؟
توقيع تمديد. النسخة الأولى من وظيفة ترميز عنوان URL الخاص بي تنتج سلاسل مثل "٪ FFFFFFA3".