بسم الله الرحمن الرحيم.
في هذا المقال سنتحدث عن المعدلات (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)
| النوع | الحجم الشائع |
|---|---|
char | 1 بايت |
short | 2 بايت |
int | 4 بايت |
long | 4 بايت (Windows) / 8 بايت (Linux x64) |
long long | 8 بايت |
float | 4 بايت |
double | 8 بايت |
long double | 16 بايت (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 للمعالجة ثم يعود.
أنواع المتغيرات
| المعدل | التخزين | النطاق | العمر |
|---|---|---|---|
auto | RAM | local/global | مع الدالة / طول البرنامج |
register | Register (وأحياناً RAM) | local فقط | مع الدالة |
static | RAM (.data/.bss) | local/global | طول البرنامج |
extern | لا مكان تخزين — mirror لتعريف في ملف آخر | local/global | طول البرنامج |
- auto: كما لو كتبت
intعادي — الافتراضي للمتغيرات المحلية. - register: طلب (Hint) للمترجم بوضع المتغير في السجل. في المترجمات الحديثة غالباً يُتجاهل — المترجم يقرر بنفسه، وإن زادت المتغيرات عن مساحة السجلات تُخزن في RAM.
- static: يُخزن في
.data(مهيأ) أو.bss(غير مهيأ) — شرحناه بالتفصيل في المقال السابق. - extern: لا يخصص ذاكرة — يشير لتعريف موجود في ملف آخر، وتُحل المشاركة في مرحلة الـ Linking.
ربط الـ RE
signed vs unsigned في الـ disassembly: القيمة
0xFFFFFFF0تُفسَّر-16كـ signed أو4294967280كـ unsigned — وهذا يغيّر قراءة الكود كلياً. في الـ assembly يظهر التمييز في تعليماتmovsx(توسيع بالإشارة) مقابلmovzx(توسيع صفري) عند نقلchar/shortإلى عرض أكبر.الـ Two’s Complement أساسي: فهم أن
0xFFFFFFFF=-1في signed هو ما يفسر لماذا نرىinc/decأو حسابات مع عناوين تنازلية في الـ disassembly. المحلل الذي لا يعرف التمثيل يخطئ في قراءة القيم الموقّعة.أنواع typedef في الـ binaries: أدوات الـ RE تعرض أنواعاً مثل
uint32_tأوDWORD— وإتقان العلاقة بين الاسم والحجم هو مرجع يومي للمحلل.الـ register والتخصيص: مستوى التحسين (
-O0مقابل-O2) يحدد أين تُخزن المتغيرات (stack vs register) — لهذا يختلف شكل الـ disassembly جذرياً مع تغيير مستوى التحسين، وسترى المتغيرات في سجلات مختلفة حسب السياق.