المكدس ومقدمة عن الإجراءات The Stack and Introduction to Procedures

الفصل السابع
المكدس  ومقدمة  عن  الإجراءات

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 فراغات تفصل بين الأعداد.
المكدس  ومقدمة  عن  الإجراءات The Stack and Introduction to Procedures

المكدس ومقدمة عن الإجراءات The Stack and Introduction to Procedures

التقييم : 5.0 الاصوات 100
شارك اللعبة :