Lesson illustration

تمثيل الأعداد الصحيحة في الذاكرة: الموقعة وغير الموقعة

مرحبًا مجددًا. في الدرس السابق تعاملت مع الثنائي والسداسي عشري بوصفهما طريقتين لكتابة القيمة نفسها، وعرفت أن البايت يتكون من 8 بتات وأن 0xFF يساوي 255 عند قراءته كعدد غير موقّع.

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


البايتات أولًا، والتفسير ثانيًا

تخيّل أن الذاكرة تحتوي البايت التالي:

11111111

هذا النمط من 8 بتات هو حقيقة مادية واحدة. لكن توجد قراءتان شائعتان له:

النمط الثنائيHexالتفسير غير الموقّعالتفسير الموقّع بالمتمم الثنائي
11111111FF255-1

إذًا ليست المسألة أن الذاكرة غيّرت البايت من 255 إلى -1؛ بل إن البرنامج أو الأداة أعطيا المجموعة نفسها من البتات معنى مختلفًا.

في التحليل العكسي، لا تقل عادةً: “هذا البايت قيمته -1” دون سياق. الأدق أن تقول:

البايت FF يساوي 255 إذا فُسّر كعدد غير موقّع من 8 بتات، ويساوي -1 إذا فُسّر كعدد موقّع من 8 بتات بالمتمم الثنائي.

هذا النوع من الدقة مهم عند قراءة حقول بنية بيانات، أو ثابت في Assembly، أو قيمة سجل في المصحح.


الأعداد غير الموقعة: كل البتات تمثل مقدارًا

العدد غير الموقّع (unsigned) لا يمكن أن يكون سالبًا. كل بت يساهم في القيمة الموجبة بالطريقة التي درستها سابقًا.

لعدد عرضه ww بت، يكون المجال:

0 إلى 2w10 \text{ إلى } 2^w - 1

أمثلة مهمة:

العرضالمجال غير الموقّع
4 بتات0 إلى 15
8 بتات، بايت واحد0 إلى 255
16 بتًا0 إلى 65535
32 بتًا0 إلى 4294967295

في بايت من 8 بتات، يحمل البت الأيسر الوزن 128:

128 64 32 16 8 4 2 1
  1  1  0  0 1 0 0 1

لذلك:

110010012=128+64+8+1=20111001001_2 = 128 + 64 + 8 + 1 = 201

وبالسداسي عشري:

1100 1001 = C9

إذًا:

0xC9=2010xC9 = 201

عند القراءة غير الموقعة فقط.

{
  "type": "exercise",
  "id": "6f239ea6-e71c-4f8d-90a4-2792e187e31f"
}

لماذا لا تكفي فكرة “بت الإشارة” البسيطة؟

قد يبدو منطقيًا أن نقول: “إذا كان البت الأيسر 1، فالعدد سالب، وخذ البتات المتبقية كمقدار.” هذا يسمى الإشارة والمقدار (sign-magnitude).

مثلًا، في 4 بتات قد تُقرأ:

1011

بوصفها 3-3: البت الأول يشير إلى السالب، والبتات 011 تساوي 3.

لكن هذا التمثيل غير مناسب للأعداد الصحيحة المعتادة داخل المعالجات، لأنه يملك تمثيلين للصفر، 0000 و1000، كما يجعل العمليات الحسابية أكثر تعقيدًا.

الحواسيب الحديثة تستخدم عادةً المتمم الثنائي (two's complement) للأعداد الصحيحة الموقعة. فيه ما يزال البت الأيسر مفيدًا لمعرفة إن كانت القيمة سالبة، لكنه ليس مجرد علامة مستقلة عن بقية العدد.

اقرأ الآن المدخل القصير التالي لتثبيت سبب الحاجة إلى تمثيل القيم السالبة، ثم ركز على جدول المتمم الثنائي وخطوات تكوينه. لا تحتاج إلى التوسع في المتمم الأحادي أو الأنظمة البديلة في هذه المرحلة.

{"type":"reading","par_intro":"اقرأ من مقال ويكيبيديا العربي لفهم لماذا لا يمكن للذاكرة أو سجلات المعالج تخزين علامة ناقص كرمز مستقل، ولمراجعة المتمم الثنائي بوصفه التمثيل العملي الشائع.","par_directions":"في الافتتاحية، اقرأ الفقرة الأولى قبل قسم «تاريخ» التي تشرح الحاجة إلى تمثيل الأعداد السالبة داخل RAM وسجلات المعالج؛ فيها <span data-type=\"resource_reading_textrange\" data-resource-subitem-id=\"8f5f9238\" data-range-start=\"RAM\" data-range-end=\"CPU\">سبب الحاجة</span> إلى التمثيل الموقّع. ثم انتقل إلى قسم «المتمم الثنائي». ابدأ بجدول البايتات الذي يقارن القراءة الموقعة وغير الموقعة، واقرأ <span data-type=\"resource_reading_textrange\" data-resource-subitem-id=\"d6ef8ed7\" data-range-start=\"00000000\" data-range-end=\"11111111\">خريطة البايت</span> كاملة، ولاحظ خصوصًا الصفوف التي تبدأ بـ `10000000`. بعد الجدول، تابع مثال تحويل `+2` إلى قيمة سالبة، واقرأ <span data-type=\"resource_reading_textrange\" data-resource-subitem-id=\"b594a234\" data-range-start=\"00000010\" data-range-end=\"11111110\">خطوات القلب</span> التي تتكون من عكس البتات ثم إضافة واحد.","learning_duration":"8 دقائق","url":"https://ar.wikipedia.org/wiki/%D8%AA%D9%85%D8%AB%D9%8A%D9%84%D8%A7%D8%AA_%D8%A7%D9%84%D8%A3%D8%B1%D9%82%D8%A7%D9%85_%D8%A7%D9%84%D9%85%D8%A4%D8%B4%D8%B1%D8%A9_(%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%85%D9%84%D8%A9_%D9%84%D9%84%D8%A7%D8%B4%D8%A7%D8%B1%D8%A9)","title":"تمثيلات الأرقام المؤشرة (المحتملة للاشارة) - ويكيبيديا","isV2":true,"blockId":"c65e4336-2eaa-4825-a80b-31ce2bccc14a","lessonId":"2b392107-edbf-494b-9a0a-3a05ccb11d71"}




المتمم الثنائي: كيف يقرأ النمط نفسه كقيمة سالبة؟

في المتمم الثنائي، يوجد عددان متساويان في عدد البتات لكنهما يختلفان في المعنى:

  • النصف الأول من الأنماط، حيث البت الأيسر 0، يمثل الصفر والأعداد الموجبة.
  • النصف الثاني، حيث البت الأيسر 1، يمثل الأعداد السالبة.

لعدد موقّع عرضه ww بتات، يكون المجال:

2w1 إلى 2w11-2^{w-1} \text{ إلى } 2^{w-1}-1

لذلك:

العرضالمجال الموقّع بالمتمم الثنائي
4 بتات-8 إلى 7
8 بتات-128 إلى 127
16 بتًا-32768 إلى 32767
32 بتًا-2147483648 إلى 2147483647

لاحظ الفرق عن المجال غير الموقّع: تخصّص الأعداد الموقعة نصف الأنماط تقريبًا للقيم السالبة.

{"type":"image","url":"https://web.stanford.edu/class/archive/cs/cs107/cs107.1224/lectures/02/IntegerRepresentations-img/img43.png","caption":"تُقارن الصورة الأنماط الثنائية نفسها عند تفسيرها كأعداد موقعة بالمتمم الثنائي في الدائرة اليسرى، وكأعداد غير موقعة في الدائرة اليمنى. مثلًا، النمط `1000` يعني -8 في اليسار و8 في اليمين، بينما `1111` يعني -1 في اليسار و15 في اليمين.","isV2":true,"blockId":"47d35822-bd98-4c71-99ae-76109f36cdc8","lessonId":"2b392107-edbf-494b-9a0a-3a05ccb11d71"}



توضح دائرة تمثيل 4 بت ثلاثة أنماط ينبغي تثبيتها:

البتاتغير موقّع، 4 بتاتموقّع، 4 بتات
011177
10008-8
111115-1

إذًا النمط 1011 الذي ذكرناه سابقًا لا يعني 3-3 في المتمم الثنائي. قيمته غير الموقعة هي 11، ولأننا نقرأه ضمن 4 بتات موقعة:

1124=1116=511 - 2^4 = 11 - 16 = -5

إذًا:

1011 = 11 unsigned = -5 signed

وهذه نقطة خطأ شائعة: لا تقرأ البت الأيسر كعلامة ناقص ثم تحول البتات الباقية إلى مقدار. تلك طريقة الإشارة والمقدار، لا المتمم الثنائي.


طريقتان لقراءة قيمة موقعة

الطريقة الأولى: اطرح قوة العدد 2 المناسبة

هذه هي الأسرع عندما تعرف طول القيمة.

لنقرأ 0xC9 كعدد موقّع من 8 بتات:

  1. كعدد غير موقّع، 0xC9 تساوي 201.
  2. البت الأعلى هو 1، إذًا القيمة سالبة في المتمم الثنائي.
  3. اطرح 282^8، أي 256:
201256=55201 - 256 = -55

إذًا:

C9 = 201 unsigned = -55 signed

القاعدة العامة هي:

S={U,if bw1=0U2w,if bw1=1S = \begin{cases} U, & \text{if } b_{w-1}=0 \\ U - 2^w, & \text{if } b_{w-1}=1 \end{cases}

حيث:

  • UU هو التفسير غير الموقّع للنمط.
  • SS هو التفسير الموقّع.
  • ww هو عدد البتات.
  • bw1b_{w-1} هو البت الأعلى.

الطريقة الثانية: اعكس البتات ثم أضف 1

هذه الطريقة تكشف مقدار العدد السالب.

لنفس القيمة:

C9 = 11001001
  1. اعكس كل البتات:
00110110
  1. أضف 1:
00110111
  1. 00110111 تساوي 55، إذًا القيمة الأصلية:
55-55

هذه الطريقة مفيدة أيضًا عندما تريد تمثيل قيمة سالبة بنفسك. مثلًا، لتمثيل 55-55 ضمن بايت:

+55  = 00110111
عكس   = 11001000
+1    = 11001001

إذًا التمثيل المخزن لـ 55-55 في 8 بتات هو:

11001001 = 0xC9
{
  "type": "exercise",
  "id": "3ef484bf-4247-4224-8e26-e0b5b264b117"
}

أمثلة تحفظها بسرعة

البايتغير موقّعموقّع بالمتمم الثنائيسبب مهم
0000الصفر له تمثيل واحد
0111موجب صغير
7F127127أعلى عدد موجب موقّع ضمن بايت
80128-128أصغر عدد موقّع ضمن بايت
C9201-55مثال نموذجي لقيمة سالبة
FE254-2قيمة سالبة قريبة من الصفر
FF255-1نمط شائع لقيمة فشل أو عداد خاص

يمكنك التحقق الذهني من FE بسهولة:

254256=2254 - 256 = -2

وكذلك:

255256=1255 - 256 = -1

تظهر FF كثيرًا في التحليل، لكن لا تفترض دائمًا أنها تعني 1-1. قد تكون قيمة 255 صحيحة تمامًا، مثل مكوّن لون أو طول أو علم ضمن حقل غير موقّع. السياق هو الذي يحسم.


ماذا يعني ذلك “داخل الذاكرة”؟

الذاكرة تخزن بايتات متتابعة، لا متغيرات تحمل تسميات مثل “هذا int سالب” أو “هذا unsigned int”. برنامج المصدر، أو تعريف البنية، أو دالة النظام، أو التعليمة المحيطة هو الذي يحدد طريقة التفسير.

عند رؤية C9 في محرر Hex أو في x64dbg، ابدأ بهذه الأسئلة:

  1. ما حجم القيمة؟ هل هي بايت واحد، أم 16 بتًا، أم 32 بتًا؟
  2. هل يتوقع السياق قيمة سالبة؟ مثل فرق، إزاحة، رمز حالة، أو نتيجة عملية حسابية.
  3. هل يوجد تعريف بنية أو توثيق API يحدد النوع؟
  4. هل خرج Ghidra أو اسم المتغير مجرد فرضية؟ في البداية اعتبره دليلاً، لا حقيقة نهائية.

مثلًا، النمط 32-بت التالي:

FF FF FF FF

إذا قُرئ كعدد غير موقّع من 32 بتًا، فقيمته:

42949672954294967295

أما إذا قُرئ كعدد موقّع من 32 بتًا بالمتمم الثنائي، فقيمته:

1-1

البايتات نفسها لم تتبدل؛ الذي تغير هو العدسة العددية التي نقرأها بها.

في هذا الدرس نتعامل مع قيم منفردة أو مع تسلسلها بوصفه نمطًا ثابتًا. في الدرس التالي ستتعلم كيف يؤثر ترتيب البايتات داخل القيمة متعددة البايتات في القراءة على أجهزة Windows ذات ترتيب Little-endian.


مختبر عملي: اعرض البايتات بطريقتين

خصص نحو 15 دقيقة داخل PowerShell على آلة Windows الافتراضية. لا تحتاج إلى برنامج خارجي أو عينة تدريبية؛ ستنشئ ملفًا صغيرًا من بايتات معروفة وتفسره يدويًا وبرمجيًا.

1. أنشئ ملفًا ثنائيًا صغيرًا

نفّذ الأوامر التالية في مجلد المختبر:

$raw = [byte[]](0x00, 0x01, 0x7F, 0x80, 0xC9, 0xFF)
[System.IO.File]::WriteAllBytes("$PWD\signed-demo.bin", $raw)

Format-Hex .\signed-demo.bin

ستظهر ستة بايتات، بالترتيب:

00 01 7F 80 C9 FF

المحتوى الثنائي للملف لا يسجل أن C9 تعني -55. إنه يسجل فقط البايت C9.

2. اطبع التفسيرين معًا

نفذ هذا المقطع:

foreach ($b in $raw) {
    $signed = if ($b -ge 128) { [int]$b - 256 } else { [int]$b }
    $bits = [Convert]::ToString([int]$b, 2).PadLeft(8, '0')

    "{0:X2}  bits={1}  unsigned={2,3}  signed={3,4}" -f $b, $bits, $b, $signed
}

ينبغي أن تلاحظ، خصوصًا، نتائج من هذا النوع:

7F  bits=01111111  unsigned=127  signed= 127
80  bits=10000000  unsigned=128  signed=-128
C9  bits=11001001  unsigned=201  signed= -55
FF  bits=11111111  unsigned=255  signed=  -1

المعادلة الموجودة في السطر الذي يحسب $signed هي تطبيق مباشر للقاعدة:

signed=unsigned256\text{signed} = \text{unsigned} - 256

لكنها تُطبّق فقط عندما تكون قيمة البايت غير الموقعة 128 أو أكبر.

3. وثق ثلاث قراءات بصيغة محلل

أضف إلى ملف ملاحظات المختبر صفوفًا مشابهة للتالي:

Offset 0x03: byte 80
Unsigned 8-bit: 128
Signed 8-bit two's complement: -128
Interpretation: cannot be finalized without program context.

اكتب ملاحظات مماثلة للبايتين C9 وFF. الهدف ليس حفظ أن FF تعني -1، بل بناء عادة توثيق النمط الخام، العرض، والتفسير المحتمل.


حدود العرض ولمحة عن الالتفاف

العرض جزء من القيمة، وليس تفصيلًا تجميليًا. النمط FF يعني 255 عند اعتباره بايتًا غير موقّعًا، لكن 0x000000FF هو النمط نفسه بعد توسيعه إلى 32 بتًا، وقيمته غير الموقعة أيضًا 255.

ومع ثبات العرض، لا توجد قيمة أعلى من الحد. على مستوى البتات، إذا أضفت 1 إلى FF ضمن بايت، تحصل على:

11111111
+       1
--------
00000000

أي يعود النمط إلى 00. هذه الفكرة تسمى الالتفاف (wraparound) على مستوى تمثيل البتات. ستصبح مهمة لاحقًا عند تحليل العدادات والتحققات والحسابات منخفضة المستوى، لكن المهم الآن هو عدم نسيان عرض القيمة أثناء تفسيرها.

{
  "type": "exercise",
  "id": "5c2ad461-da71-4468-bb9a-8dbde21beb15"
}

الخلاصة

أصبحت تستطيع الآن قراءة نمط البتات من زاويتين أساسيتين:

  • العدد غير الموقّع من ww بتات يمتد من 0 إلى 2w12^w - 1.
  • العدد الموقّع بالمتمم الثنائي من ww بتات يمتد من 2w1-2^{w-1} إلى 2w112^{w-1}-1.
  • إذا كان البت الأعلى في قيمة موقعة يساوي 0، فالقراءة الموقعة وغير الموقعة متطابقتان.
  • إذا كان يساوي 1، احسب القيمة الموقعة بطرح 2w2^w من القراءة غير الموقعة، أو اعكس البتات وأضف 1 لاستخراج المقدار السالب.
  • الذاكرة تخزن بتاتًا فقط؛ نوع المتغير والسياق المحيط هما ما يحددان إن كان النمط موقّعًا أو غير موقّع.
  • FF قد يساوي 255 أو 1-1، وC9 قد يساوي 201 أو 55-55، وفقًا للتفسير.

في الدرس التالي ستنتقل من بايت واحد إلى قيم متعددة البايتات، وستتعلم لماذا قد تظهر القيمة 0x12345678 في الذاكرة على هيئة بايتات بترتيب يبدو معكوسًا: Little-endian.

Can't find a good explanation? Sign up and we'll make it for you