下面使用 c 标准库的函数 scanf() 来从标准输入读取数值,并使用 printf() 输出读到的值。
# cio-2.s
# using c function printf() and scanf()
.section .rodata
prompt:
.asciz "enter an integer: "
output:
.asciz "your input is %d.\n"
format:
.asciz "%d"
.section .data
num:
.int 0
.section .text
.globl main
main:
pushl $prompt
call printf
addl $4, %esp
pushl $num
pushl $format
call scanf
addl $8, %esp
pushl num
pushl $output
call printf
addl $8, %esp
pushl $0
call exit
第 4-10 行定义了一些字符串常量。
12-14 行:
.section .data
num:
.int 0
定义了一个变量 num,它的类型是整型,初始化值为 0。.int 与 .asciz 等相似,用于定义数据的类型,一般情况下 .int 的大小是 4 个字节。除了这里出现的 .asciz,.int 和前面提到过的 .ascii 外,还有 .byte,.float 等标识符可以用来定义不同的数据类型。
num 位于 .data 段,与 .rodata 不同的是,.data 段的内容可以被修改。
第 19,20 行输出一段提示信息。第 21 行:
addl $4, %esp
加法指令 addl 把 esp 的值加 4。add 系列指令的一般格式是:
add source, destination
计算 destination + source 的值,并把结果保存在 destination 中。与 mov 系列指令类似,我们在 add 后面加上不同的字母表示操作数的宽度,例如 addl 表示两个 32 位的数相加,而 addw 则表示两个 16 位的数相加,等等。
在 上半部分 中提到用完栈后应该及时恢复使用前的 esp 的值。这里我们只想恢复 esp 的值,并不关心上次调用的时候压入栈的值是什么,所以我们只需要把 esp 的值增加 4(上半部分 提到在 x86 上栈的增长方式是从高地址向低地址增长的),因为在调用 printf() 前我们为把 prompt 的 4 字节地址压入栈中。这条语句对于 esp 来说和上一篇提到的“popl %eax”是等价的,都是把 esp 的值加 4:
+----+ +----+
| .. | | .. |
+----+ +----+
| 10 | esp after 'addl %4, %esp' --> | 10 |
+----+ +----+
| 12 | | 12 |
+----+ +----+
| 34 | | 34 |
+----+ +----+
^ | 56 | | 56 |
| +----+ +----+
memory | 78 | <-- esp after 'pushl $prompt' | 78 |
addresses +----+ +----+
接下来为 scanf() 准备参数。语句
pushl $num
pushl $format
先后把 num 和 format 的地址压入栈中。在 c 语言中 scanf() 应该是这样的:
scanf("%d", &num);
format 和 &num 分别是 scanf() 的第一和第二个参数。c 语言函数参数的入栈顺序是相反的,也就是说第一个参数最后入栈,最后一个参数第一个入栈(想想 scanf() 的不定参数是怎样实现的?)。
然后调用 scanf(),运行完之后再恢复 esp 的值。因为这里入栈的是两个 32 位的地址值,所以 esp 加 8 而不是加 4。
第 28-30 行调用 printf() 输出 scanf() 读入的值:
pushl num
pushl $output
call printf
这里的第一条语句把 num 的值而不是 num 的地址($num)压入栈中,因为 printf() 的调用应该是这样的:
printf("your input is %d\n", num);
最后程序的运行情况:
enter an integer: 5
your input is 5
近期评论