الفصل السابع
المكدس ومقدمة عن الإجراءات
The Stack and Introduction to Procedures
يتم استخدام مقطع المكدس للتخزين المؤقت للعناوين والبيانات أثناء عمل البرنامج وفى هذا الفصل سنتناول طريقة عمل المكدس واستخدامه في عملية النداء للبرنامج الفرعية Procedures وذلك لتوضيح كيفية وضع قيم في المكدس وأخذ قيم منه باستخدام الأوامر pop, push ثم نتطرق لميكانيكية نداء البرامج الفرعية مع توضيح مثال لذلك.
يعتبر المكدس كمصفوف أحادي في الذاكرة ويتم التعامل مع طرف واحد فقط منه حيث يتم إضافة العنصر في قمة المكدس ويتم أخذ آخر عنصر في عملية السحب التالية بمعني انه يعمل بطريقة آخر مدخل هو أول مخرج LIFO (Last In first out) يجب على كل برنامج أن يقوم بتحديد منطقة في الذاكرة وتعمل كمكدس كما ذكرنا في الفصول السابقة وذلك باستخدام الأمر.
STACK 100h
حيث يشير مسجل مقطع المكدس SS إلى مقطع المكدس في المثال السابق ويحتوى مؤشر المكدس SP على القيمة 100h وهى تشير إلى مكدس خالي وعند وضع قيم فيه يتم إنقاص هذه القيمة.
وضع قيم في المكدس والأوامر PUSH , PUSHF:
يتم استخدام الأمر PUSH لإدخال قيمة في المكدس وصيغته
PUSH SOURCE
حيث المصدر هو مسجل أو موقع في الذاكرة بطول 16 خانة . مثلاً
PUSH AX
ويتم في هذه العملية الآتي:
1- إنقاص قيمة مؤشر المكدس SP بقيمة 2
2- يتم وضع نسخة من المصدر في الذاكرة في العنوان SS:SP
لاحظ أن محتويات المصدر لا يتم تغييرها.
الأمر PUSHF يقوم بدفع محتويات مسجل البيارق في المكدس. فمثلاً لو كانت محتويات مؤشر المسجل SP هى الرقم 100h قبل تنفيذ العملية فبعد تنفيذ الأمر PUSHF يتم إنقاص 2 من محتويات المسجل SP لتصبح قيمته 00FEh بعد ذلك يتم عمل نسخة من محتويات مسجل البيارق في مقطع المكدس عند الإزاحة 00FE.
سحب قيمة من المكدس والأوامر POP , POPF:
لسحب قيمة من المكدس يتم استخدام الأمر POP وصيغته
POP Destination
حيث المستودع عبارة عن مسجل 16 خانة ( ماعدا المسجل IP) أو خانة في الذاكرة مثلاً POP BXوتنفيذ الأمر POP يتضمن التالي:
1- نسخ محتويات الذاكرة من العنوان SS:SP الى المستودع
2- زيادة قيمة مؤشر المكدس SP بالقيمة 2
الأمر POPF يقوم بسحب أول قيمة من المكدس إلى مسجل البيارق.
لاحظ أن أوامر التعامل مع المكدس لا تؤثر في البيارق كما أنها تتعامل مع متغيرات بطول 16 خانة ولا تتعامل مع 8 خانات. فمثلاً الأمر التالي غير صحيح
Push AL ; ILLEGAL
بالإضافة إلى برنامج المستخدم User Program يقوم نظام التشغيل باستخدام المكدس لأداء عمله فمثلاً عند استخدام نداء المقاطعة INT 21h يقوم نظام التشغيل بتخزين القيم المختلفة للمسجلات في المكدس ثم استرجاعها مره أخرى عند الانتهاء من عمل نداء المقاطعة والعودة للبرنامج وبالتالي لا يتأثر برنامج المستخدم بالتغييرات التي تمت في المسجلات.
مثال لتطبيقات استخدام المكدس:
لان نظرية عمل المكدس تعتمد على أن آخر قيمة تم تخزينها هي أول قيمة سيتم سحبها LIFO ستقوم في هذا الجزء بتوضيح مثال يقوم بقراءة جملة من لوحة المفاتيح. يقوم البرنامج في السطر التالي بطباعة الجملة بصورة عكسية مثال للتنفيذ:
? this is a test
tset a si siht
والخوارزمية هي:
Display ‘?’
Initialize count to 0
Read a character
While Character is not a Carriage return Do
push character onto the stack
increment counter
Read a character
End_While
Go to New line
For count times Do
Pop a character from the stack
Display it
End_For
يستخدم البرنامج المسجل CX للاحتفاظ بعدد حروف الجملة التي تم إدخالها بعد الخروج من حلقة while يكون عدد الحروف الموجودة في المسجل CX وتكون كل الحروف التي تم إدخالها موجودة في المكدس بعد ذلك يتأكد البرنامج من انه قد تم إدخال حروف وذلك بالتأكد من أن المسجل CX لا يساوى صفر.
.MODEL SMALL
.STACK 100H
.CODE
MAIN PROC
; display user prompt
MOV AH,2
MOV DL,'?'
INT 21H
;initialize character count
XOR CX , CX
;read character
MOV AH , 1
INT 21H
;while character is not a carriage return do
WHILE_:
CMP AL , 0DH
JE END_WHILE
PUSH AX
INC CX
INT 21H
JMP WHILE_
END_WHILE:
MOV AH , 2
MOV DL , 0DH
INT 21H
MOV DL , 0AH
INT 21H
JCXZ EXIT
TOP:
POP DX
INT 21H
LOOP TOP
EXIT: MOV AH , 4CH
INT 21H
MAIN ENDP
END MAIN
البرامج الفرعية PROCEDURES:
عند كتابة البرنامج وبالذات الكبيرة منها يتم تقسيم البرنامج إلى مجموعة البرامج الفرعية الصغيرة والتي تسهل كتابتها ويكون عمل هذه البرامج الفرعية كوحدة مستقلة لها مدخلات وتؤدى وظيفة محدودة ولها مخرجات محدده وواضحة وبالتالي يسهل استعمالها وكذلك إعادة استخدامها في برامج أخرى كما سنرى فيما بعد.
وبالتالي فان طريقة كتابة البرامج تبدأ بتقسيم المشكلة إلى مجموعة من البرامج الصغيرة ثم توزيع هذه البرامج الصغيرة وكتابة كل منها على حده واختباره وبعد ذلك يتم تجميع هذه البرامج الصغيرة لتعطى برنامج كبير.
أحد هذه البرامج الصغيرة هو البرنامج الرئيسي وهو يعتبر نقطة الدخول للبرنامج ويقوم بدوره بنداء البرامج الفرعية الأخرى والتي يقوم كل منها بدوره بعد الانتهاء بالعودة إلى البرنامج الذي قام باستدعائه. وفى حالة البرامج ذات المستوى العالي High Level Programming Languages تكون عملية النداء والعودة مخفية عن المبرمج ولكن في لغة التجميع يجب كتابة أمر الاستدعاء CALL أمر العودة RET كما سنرى عند التعامل مع البرامج الفرعية.
التصريج عن البرامج الفرعية Procedure Declaration:
يتم التصريح عن البرنامج الفرعي على النحو التالي:
Name PROC type
; Body of the procedure
RET
Name ENDP
حيثName هو اسم الإجراء و type هو معامل Operand اختياري ويأخذ الصيغتين NEAR أو FAR حيث NEAR تعنى أن نداء البرنامج الفرعي يتم من داخل نفس المقطع أما FAR فتعنى إن نداء البرنامج الفرعي يتم من مقطع مختلف. وإذا لم يتم كتابة شئ يتم افتراض أن البرنامج الفرعي من النوع NEAR.
الأمر RET ( Return) يؤدى إلى إنهاء البرنامج الفرعي والعودة إلى البرنامج الذي قام باستدعائه . وأي برنامج فرعى يجب أن يقوم باستخدام الأمر RET للعودة إلى البرنامج الذي قام استدعاؤه ( فيما عدا البرنامج الرئيسي) ويتم هذا عادة في آخر جملة في البرنامج الفرعي.
الاتصال بين البرامج الفرعية
يجب على أي برنامج فرعى أن تكون له إمكانية استقبال المدخلات إليه وان يقوم بإعادة النتيجة إلى البرنامج الذي قام بندائه إذا كان عدد المدخلات والمخرجات صغير يمكن استخدام المسجلات كأماكن يتم عن طريقها الاتصال بين البرامج الفرعية المختلفة أما إذا كان عدد المدخلات أو المخرجات كبير نضطر إلى استخدام طرق أخري سيتم مناقشتها في الفصول التالية.
توثيق البرامج الفرعية
يجب بعد الانتهاء من كتابة البرنامج الفرعي القيام بعملية التوثيق الكامل له حتى يسهل في أي وقت وبواسطة أي شخص استخدام هذا البرنامج الفرعي إذا أراد ذلك ويشمل التوثيق على:
1- الشرح العام للوظيفة التي يقوم بها البرنامج الفرعي
2- المدخلات: يتم فيها تعريف المدخلات المختلفة للبرنامج الفرعي
3- المخرجات:يتم فيها تعريف المخرجات المختلفة للبرنامج الفرعي
4- الاستخدامات يتم توضيح البرامج الفرعية (إن وجدت) والتي يقوم هذا
البرنامج الفرعي باستخدامها.
الامر RET , CALL:
لنداء برنامج يتم استخدام الأمر CALL وله صيغتين الأولي مباشر DIRECT وهى على النحو التالي
CALL name
حيث name هو اسم البرنامج الفرعي المطلوب نداؤه. والصيغة الثانية للنداء الغير مباشر Indirect وهى على الصورة
CALL address_expression
حيث CALL address - expression تحدد المسجل أو المتغير الذي يحوى عنوان البرنامج الفرعي المطلوب تنفيذه.
عند نداء برنامج فرعى يتم الآتي
1- يتم تخزين عنوان الرجوع Return address في المكدس وهو الأمر التالي
للأمر CALL في البرنامج الذي قام بالنداء
2- يتم وضع عنوان إزاحة أول أمر في البرنامج الفرعي في المسجل
التعليمات IP وبالتالي يتم التفرع إلى ذلك البرنامج الفرعي
وللعودة من أي برنامج فرعي نستخدم الأمر RET حيث تؤدى إلى اخذ عنوان الرجوع من المكدس ووضعه فى مسجل التعليمات مما يؤدى إلى العودة للبرنامج الذي قام بالنداء
ويمكن ان يأخذ الصورة RET Pop_value
حيث Pop_value معامل اختياري. إذا كانت Pop_value = N فان معنى ذلك أن يتم سحب عدد N -Bytes إضافية من المكدس.
مثال لبرنامج فرعى:-
سنوضح هنا مثال لبرنامج فرعي يتم فيه حساب حاصل ضرب رقمين موجبين a,b وذلك باستخدام عملية الجمع والإزاحة وتكون خوارزمية الضرب علي النحو التالي :-
Product = 0
Repeat
If LSB of B is 1 then
Product = Product + A
End_if
Shift left A
Shift right B
until B = 0
ولمتابعة الخوارزمية اعتبر ان 111b =A و1101b = Bوبتطبيق الخوارزمية نجد ان
product = 0
since LSB of B is 1 , product = 0 + 111b = 111b
shift left A: A = 1110b
shift right B : B = 110b
since LSB of B is 0 ;
shift left A : A=11100b
shift right B : B = 11b
since LSB of B is 1 ; product = 111b + 11100b = 100011b
shift left A : A = 111000b
shift right B : B =1b
since LSB of B is 1 , product = 100011b + 111000b = 1011011b
shift left A : A = 1110000
shift right B : B = 0
since LSB of B is 0 ,
return Product = 1011011b = 91d
وفيما يلي البرنامج:
.MODEL SMALL
.STACK 100H
.CODE
MAIN PROC
CALL MULTIPLY
MOV AH,4CH
INT 21H
MAIN ENDP
MULTIPLY PROC
PUSH AX
PUSH BX
XOR DX , DX
REPEAT:
TEST BX , 1
JZ END_IF
ADD DX , AX
END_IF:
SHL AX , 1
SHR BX , 1
JNZ REPEAT
POP BX
POP AX
RET
MULTIPLY ENDP
END MAIN
هنا يقوم الإجراء باستقبال المدخلات في المسجلين AX و BX ويتم حساب حاصل الضرب في المسجل DX. وتجنبنا لحدوث الفيضان يحتوى المسجلان AX وBX على رقمين أقل من FFh.
يبدأ دائماً أي برنامج فرعى بتخزين قيم المسجلات التي سيقوم باستخدامها في المكدس باستخدام مجموعه من أوامر PUSH ثم بعد انتهاء عمل الإجراء يتم استرجاع القيم القديمة من المكدس باستخدام مجموعة من أوامر pop وذلك فيما عدا المسجلات التي يقوم بإرجاع النتيجة فيها وذلك حتى لا يتم تغيير المسجلات للبرنامج الأصلي وبالتالي فان الشكل العام للبرامج الفرعية هو:
NAME PROC
Push AX
Push BX
: الأوامر داخل الإجراء
Pop BX
Pop AX
RET
NAME ENDP
تمارين:
1- إذا كان تعريف المكدس في البرنامج هو .STACK 100H
أ- ما هي محتويات مؤشر المكدس SP بعد بداية تنفيذ البرنامج مباشرة؟
ب- افترض أن المسجلات التالية تحتوى على القيم الموضحة
AX = 1234h , BX = 5678h , CX = 9ABCh , and SP=100h
وضح محتويات المسجلات SP , CX , BX , AX بعد تنفيذ الجزء التالي البرنامج
PUSH AX
PUSH BX
XCHG AX , CX
POP CX
PUSH AX
POP BX
3- عندما يمتلئ المكدس تكون محتويات مؤشر المكدس هل الرقم صفر ( SP=0 ). اذا
تم وضع كلمة جديدة في المكدس. ماذا سيحدث للمسجل SP ؟ وماذا يمكن
أن يحدث للبرنامج.
4- افترض أن برنامج به الجزء التالي:
CALL PROC1
MOV AX , BX
افترض أن:
أ- الأمر MOV AX,BX يقع في الذاكرة في العنوان 08FD:0203
ب- البرنامج PROC1 من النوع Near ويقع في العنوان 08FD:300h
ج- يحتوى مؤشر المكدس على القيمة SP = 010Ah
ما هي محتويات المسجلين SP , IP بعد تنفيذ الأمر CALL PROC1 مباشر وما هي
الكلمة الموجودة في قمة المكدس.
5- اكتب برنامج يقوم بكل الآتي:
أ- وضع الكلمة الموجودة في قمة المكدس في المسجل AX دون تغيير
محتويات المكدس.
ب- وضع الكلمة الثانية في المكدس في المسجل CX بدون تغيير محتويات
المكدس.
جـ - استبدال محتويات الكلمة الأولى في المكدس مع الكلمة الثانية
6 - في المعادلات الجبرية يمكن استخدام الأقواس لتوضيح عملية محددة وتحديد أولويات الحساب
حيث نستخدم الأقواس ‘( ) { } [ ] ‘ وتنتهي المعادلة بالضغط علي مفتاح الإدخال. للتأكد
من صحة وجود الأقواس يجب أن يكون نوع كل قوس من نفس نوع آخر قوس تم فتحه.
فمثلاً المعادلة التالية صحيحة
( A + { B - ( D - E ) + [ A + B ] } )
بينما المعادلة التالية غير صحيحة
( A + { B - C ] )
يمكن التأكد من المعادلة باستخدام المكدس حيث نقوم بقراءة المعادلة من اليسار وكلما
وجدنا قوس جديد يتم إدخاله في المكدس. إذا كان القوس هو قوس إغلاق يتم مقارنته مع
آخر قوس في المكدس بعد إخراجه منه فإذا كانا من نفس النوع نواصل القراءة وإذا لم يكن
من نفس النوع يعني ذلك أن المعادلة خطأ. في النهاية إذا تم تفريغ كل الأقواس من المكدس
تكون المعادلة صحيحة وإذا ظلت هناك أقواس في المكدس تكون المعادلة غير صحيحة.
أكتب برنامج يقوم بقراءة معادلة تحتوي علي الأنواع الثلاثة من الأقواس المذكورة. يستمر
البرنامج الإدخال حتى تنتهي المعادلة أو يقوم المستخدم بإدخال معادلة خطأ حيث يقوم
البرنامج في هذه الحالة بإخطار المستخدم بأن المعادلة خطأ.
7 - نستخدم الطريقة التالية لتوليد أرقام عشوائية في المدى من 1 إلى 32767
- ابدأ بأي رقم.
- قم بإزاحة الرقم لليسار خانة واحدة.
- استبدل الخانة رقم صفر بالخانتين 14 و 15 بعد عمل XOR لهما.
- قم بوضع الرقم صفر في الخانة 15.
المطلوب كتابة الإجراءات التالية:
أ - إجراء يسمي READ وهو يقرأ رقم ثنائي من المستخدم ويقوم بتخزينه في المسجل BX
ب - إجراء يسمي RANDOM وهو يستقبل عدد في المسجل BXويقوم بإعادة رقم عشوائي
حسب الخوارزمية المذكورة
جـ – إجراء يسمي WRITE وهو يقوم بطباعة محتويات المسجل BX في الصورة الثنائية.
أكتب برنامج يقوم بطباعة علامة الاستفهام ‘?’ ثم يقوم بنداء الإجراء READ لقراءة رقم ثنائي ثم نداء الإجراء RANDOM لحساب الرقم العشوائي ثم نداء الإجراء WRITE لحساب
وطباعة 100 رقم عشوائي بحيث يتم طباعة 4 أرقام فقط في السطر الواحد مع 4 فراغات تفصل بين الأعداد.
|