التزايد من 0 إلى 100 في لغة التجميع
سؤال
هذا أمر غريب نوعًا ما، لكنني كنت أتفحص مجمع GNU اليوم (أريد أن أكون قادرًا على قراءة بناء الجملة على الأقل)، وكنت أحاول تشغيل هذا المثال الصغير المبتكر الخاص بي.على وجه التحديد، أريد فقط الانتقال من 0 إلى 100، وطباعة الأرقام طوال الوقت.لذلك بعد بضع دقائق توصلت إلى هذا:
# count.s: print the numbers from 0 to 100.
.text
string: .asciz "%d\n"
.globl _main
_main:
movl $0, %eax # The starting point/current value.
movl $100, %ebx # The ending point.
_loop:
# Display the current value.
pushl %eax
pushl $string
call _printf
addl $8, %esp
# Check against the ending value.
cmpl %eax, %ebx
je _end
# Increment the current value.
incl %eax
jmp _loop
_end:
كل ما أحصل عليه من هذا هو 3 طبعت مرارًا وتكرارًا.كما قلت، مجرد مثال مفتعل، لذا لا تقلق كثيرًا بشأنه، فهو ليس مشكلة حياة أو موت.
(التنسيق معطل قليلاً، لكن لا يوجد شيء كبير).
المحلول
لا يمكنك الوثوق بما يفعله أي إجراء يسمى لأي من السجلات.قم إما بدفع السجلات إلى المكدس وإزالتها مرة أخرى بعد استدعاء printf أو الاحتفاظ بقيم الزيادة ونقطة النهاية في الذاكرة وقراءتها/كتابتها في السجلات حسب حاجتك إليها.
وآمل أن يعمل التالية.أفترض أن Pushl يحتوي على Popl مكافئ ويمكنك دفع رقمين إضافيين إلى المكدس.
# count.s: print the numbers from 0 to 100.
.text
string: .asciz "%d\n"
.globl _main
_main:
movl $0, %eax # The starting point/current value.
movl $100, %ebx # The ending point.
_loop:
# Remember your registers.
pushl %eax
pushl %ebx
# Display the current value.
pushl %eax
pushl $string
call _printf
addl $8, %esp
# reinstate registers.
popl %ebx
popl %eax
# Check against the ending value.
cmpl %eax, %ebx
je _end
# Increment the current value.
incl %eax
jmp _loop
_end:
نصائح أخرى
لست على دراية بـ _printf، ولكن هل من الممكن أن يقوم بتعديل eax؟يجب أن يُرجع Printf عدد الأحرف المطبوعة، وهو في هذه الحالة اثنان:"0" و" ".أعتقد أنه يُرجع هذا في eax، وعندما تقوم بزيادته، تحصل على 3، وهو ما تبدأ في طباعته.قد يكون من الأفضل لك استخدام سجل مختلف للعداد.
يمكنك استخدام السجلات "المحفوظة لدى المستدعي" بأمان دون الحاجة إلى حفظها بنفسك.على x86، هذه هي edi وesi وebx؛البنى الأخرى لديها المزيد.
تم توثيقها في مراجع ABI: http://math-atlas.sourceforge.net/devel/assembly/
عادةً ما تقوم الوظائف المكتوبة جيدًا بدفع جميع السجلات إلى المكدس ثم تفرقعها عند الانتهاء بحيث تظل دون تغيير أثناء الوظيفة.سيكون الاستثناء هو eax الذي يحتوي على القيمة المرجعة.من المرجح أن تكون وظائف المكتبة مثل printf مكتوبة بهذه الطريقة، لذلك لن أفعل كما يقترح ويدج:
ستحتاج إلى القيام بنفس الشيء بالنسبة لأي متغير آخر لديك.يعد استخدام السجلات لتخزين المتغيرات المحلية محجوزًا إلى حد كبير للبنيات التي تحتوي على سجلات كافية لدعمها (على سبيل المثال.EPIC، AMD64، إلخ.)
في الواقع، حسب ما أعرفه، عادة ما يقوم المترجمون بتجميع الوظائف بهذه الطريقة للتعامل مع هذه المشكلة بالضبط.
@ seanyboy، الحل الخاص بك هو مبالغة.كل ما هو مطلوب هو استبدال eax ببعض السجلات الأخرى مثل ecx.
ناثان على الطريق الصحيح.لا يمكنك افتراض أن قيم التسجيل لن يتم تعديلها بعد استدعاء روتين فرعي.في الواقع، من الأفضل افتراض أنه سيتم تعديلها، وإلا فلن يتمكن الروتين الفرعي من القيام بعمله (على الأقل بالنسبة للبنيات ذات عدد التسجيل المنخفض مثل x86).إذا كنت تريد الاحتفاظ بقيمة ما، فيجب عليك تخزينها في الذاكرة (على سبيل المثال:ادفعه إلى المكدس وتتبع موقعه).
ستحتاج إلى القيام بنفس الشيء بالنسبة لأي متغير آخر لديك.يعد استخدام السجلات لتخزين المتغيرات المحلية محجوزًا إلى حد كبير للبنيات التي تحتوي على سجلات كافية لدعمها (على سبيل المثال.EPIC، AMD64، إلخ.)
يمكنك إعادة كتابتها بحيث تستخدم السجلات التي لا يفترض أن تتغير، على سبيل المثال %ebp
.فقط تأكد من دفعهم إلى المكدس في البداية، وإخراجهم في نهاية روتينك.
# count.s: print the numbers from 0 to 100.
.text
string: .asciz "%d\n"
.globl _main
_main:
push %ecx
push %ebp
movl $0, %ecx # The starting point/current value.
movl $100, %ebp # The ending point.
_loop:
# Display the current value.
pushl %ecx
pushl $string
call _printf
addl $8, %esp
# Check against the ending value.
cmpl %ecx, %ebp
je _end
# Increment the current value.
incl %ecx
jmp _loop
_end:
pop %ebp
pop %ecx