P01NT
0x15 متقدم 16/19

المعدلات

معدلات الإشارة والحجم والتخزين signed/short/static، وتمثيل الأعداد السالبة بتعويض الاثنين.

· 4 دقائق قراءة · 752 كلمة
المحتويات

بسم الله الرحمن الرحيم.

في هذا المقال سنتحدث عن المعدلات (Data Modifiers) — وهي التي تغيّر خصائص وشكل الـ Data Types:

  • Sign: signed, unsigned.
  • Size: short, long.
  • Storage: auto, register, extern, static.

ملاحظات عامة

  • لا يمكن استخدام أكثر من معدّل من نفس النوع معاً (لا يمكن long short مع بعض).
  • بعض المعدلات لا تصح في مواضع معينة — مثل unsigned float، لأن الـ float دائماً signed.

1. معدلات الإشارة (Sign Modifiers)

كيف نمثل متغير signed داخل الـ Data Type؟ تطورت ثلاث طرق عبر الزمن:

(1) Signed Magnitude

أول 7 بتات للقيمة، وآخر بت (الأعلى) للإشارة — 0 موجب، 1 سالب:

 5: 00000101
-5: 10000101

عيوب قاتلة:

  • للصفر تمثيلان: +0 و -0.
  • العمليات الحسابية تختل: الـ ALU تعرف الجمع فقط، فماذا يحدث لجمع +1 مع -1؟
+1: 00000001
-1: 10000001
    10000010 = -2   ← خطأ! المفروض 0

الحل المؤقت كان شروطاً: إذا P == Q فالنتيجة 0، وإذا P > Q اطرح، وهكذا — تعقيد غير مقبول.

(2) One’s Complement

قلب كل البتات (كما لو عملت NOT):

+1: 00000001
-1: 11111110

نفس المشاكل: +0 و -0 موجودان، وجمع +1 مع -1 يعطي -0 (11111111).

(3) Two’s Complement — الطريقة المعتمدة

إذا كان أول بت 1 (الرقم سالب)، اقلب بقية البتات وأضف 1:

 5: 00000101
-5: 11111011

لماذا الأفضل؟

  • صفر واحد فقط — لا يوجد +0 و -0.
  • الـ ALU تجمع فقط: جمع 5 مع -5 يعطي overflow يُتجاهل، وتبقى 8 بتات = 0.
  • الطرح = جمع المتمم — ولهذا اعتمدته كل المعالجات الحديثة (x86, ARM…).
  00000101  (5)
+ 11111011  (-5)
 ----------
 100000000  → نأخذ 8 بتات = 00000000 = 0

قواعد مهمة

  • unsigned ينطبق على كل الأنواع الصحيحة: char, short, int, long, long long.
  • float و double موقّعان دائماً — لا توجد نسخة unsigned منهما.

2. معدلات الحجم (Size Modifiers)

النوعالحجم الشائع
char1 بايت
short2 بايت
int4 بايت
long4 بايت (Windows) / 8 بايت (Linux x64)
long long8 بايت
float4 بايت
double8 بايت
long double16 بايت (GCC/Linux) / 8 بايت (MSVC)
  • long و short افتراضياً int (أي long int و short int).
  • مهم: أحجام long و long double تعتمد على المنصة والمترجم — كما ذكرنا في مقال أنواع البيانات.

مشكلة الاختلاف بين المترجمات — الحل: typedef

ماذا لو قال مترجم أن char = 1 بايت وآخر = 2 بايت؟ النتيجة أخطاء في نقل البرنامج. الحل: أنواع معيارية عبر typedef وبـ naming convention (نوع + عدد البتات):

// stdtypes.h
typedef unsigned char  u8;   // unsigned 8-bit  = 1 بايت
typedef unsigned short u16;  // unsigned 16-bit = 2 بايت
typedef unsigned int   u32;  // unsigned 32-bit = 4 بايت
typedef unsigned long long u64; // unsigned 64-bit = 8 بايت

الاستخدام:

u16 varName; // متغير غير موقّع بعرض 16 بت

ملاحظة RE: هذه الأنماط هي أساس أنواع Windows SDK: BYTE (1)، WORD (2)، DWORD (4)، QWORD (8) — وستراها في كل تحليل PE و API.

3. معدلات التخزين (Storage Modifiers)

عند تشغيل الكود، البيانات قد تنتقل بين الـ Register والـ Cache والـ RAM — حسب الحجم والتعريف. في الـ low-level: المتغير ينتقل من RAM إلى Register للمعالجة ثم يعود.

أنواع المتغيرات

المعدلالتخزينالنطاقالعمر
autoRAMlocal/globalمع الدالة / طول البرنامج
registerRegister (وأحياناً RAM)local فقطمع الدالة
staticRAM (.data/.bss)local/globalطول البرنامج
externلا مكان تخزين — mirror لتعريف في ملف آخرlocal/globalطول البرنامج
  • auto: كما لو كتبت int عادي — الافتراضي للمتغيرات المحلية.
  • register: طلب (Hint) للمترجم بوضع المتغير في السجل. في المترجمات الحديثة غالباً يُتجاهل — المترجم يقرر بنفسه، وإن زادت المتغيرات عن مساحة السجلات تُخزن في RAM.
  • static: يُخزن في .data (مهيأ) أو .bss (غير مهيأ) — شرحناه بالتفصيل في المقال السابق.
  • extern: لا يخصص ذاكرة — يشير لتعريف موجود في ملف آخر، وتُحل المشاركة في مرحلة الـ Linking.

ربط الـ RE

  1. signed vs unsigned في الـ disassembly: القيمة 0xFFFFFFF0 تُفسَّر -16 كـ signed أو 4294967280 كـ unsigned — وهذا يغيّر قراءة الكود كلياً. في الـ assembly يظهر التمييز في تعليمات movsx (توسيع بالإشارة) مقابل movzx (توسيع صفري) عند نقل char/short إلى عرض أكبر.

  2. الـ Two’s Complement أساسي: فهم أن 0xFFFFFFFF = -1 في signed هو ما يفسر لماذا نرى inc/dec أو حسابات مع عناوين تنازلية في الـ disassembly. المحلل الذي لا يعرف التمثيل يخطئ في قراءة القيم الموقّعة.

  3. أنواع typedef في الـ binaries: أدوات الـ RE تعرض أنواعاً مثل uint32_t أو DWORD — وإتقان العلاقة بين الاسم والحجم هو مرجع يومي للمحلل.

  4. الـ register والتخصيص: مستوى التحسين (-O0 مقابل -O2) يحدد أين تُخزن المتغيرات (stack vs register) — لهذا يختلف شكل الـ disassembly جذرياً مع تغيير مستوى التحسين، وسترى المتغيرات في سجلات مختلفة حسب السياق.