本节必须掌握的知识点:
示例二源代码
代码分析
汇编解析
2.1.1 示例二
■计算整数和并显示
示例二计算整数和并显示结果。鼠标选中VS左侧解决方案资源管理器“源文件”,点击鼠标右键,选择“添加”>“新建项(W)…”,新建项目2-1-1.c。编辑源代码如下所示:
代码示例2 |
/*
显示整数11和22的和
*/
#include <stdio.h>//C标准库输入输出头文件
int main(void)//函数头
{
printf("%d", 11 + 22); //%d用十进制数显示整数11+22的和
return 0;//结束返回
}
●输出结果:33
2.1.2 代码分析
●注释
多行注释:/* ……*/ 之间的部分称为注释。
单行注释://。
printf ("%d",11+22); //格式化说明符%d用于十进制数显示整数11+22的和。
●printf函数:格式化输出函数。
printf函数可以在显示器控制台窗口进行输出操作,打印format格式化字符串。
printf函数的第一个实参“%d”为format格式化常量字符串,指定了输出格式为十进制数格式 。第二个实参为一个整数算术表达式“11+22”,由编译器计算出结果,并替换第一个参数中的“%d”。
printf函数为可变参数函数,参数的个数不确定,参数的个数根据实际需要确定。参数的个数等于格式化说明符的个数加上format格式化常量字符串。包含两个以上参数时用逗号分隔。
提示
1.C语言语句以分号结尾。
2.{…}之间的语句按顺序执行。
2.1.3 汇编解析
■汇编代码(makefile文件略)
;C标准库头文件和导入库
include vcIO.inc
.data
szMsg db "%d",0
.code
start:
invoke printf,offset szMsg,11+22 ;控制台窗口输出整数和
invoke _getch ;等待输入单个字符
ret ;结束返回
end start
上述代码为2-1-1.c的汇编代码实现。vcIO.inc头文件在示例一中已经详细讲述,此处不再赘述。
.data数据段定义了格式化常量字符串szMsg。
.code代码段语句:invoke printf,offset szMsg,11+22;控制台窗口输出整数和
调用printf函数按照十进制整数格式输出11+22的整数和。该语句为高级汇编写法,等价于下面的汇编语句:
mov eax,11 ;eax累加器
add eax,22 ;加法运算
push eax ;累加和入栈
push offset szMsg ;格式化常量字符串偏移地址入栈
call printf ;调用printf函数输出结果
上述代码先计算11+22的和,保存在累加器eax中,然后压入堆栈传参,接着将格式化常量字符串的偏移地址入栈,最后call指令调用printf函数。
■反汇编代码
VS中在printf一行按F9下断点,然后按F5调试执行,反汇编窗口的反汇编代码如下:
printf("%d", 11 + 22); //%d用十进制数显示整数11+22的和
01141838 push 21h ;十六进制数21h等于十进制数33
0114183A push offset string "%d" (01147B30h) ;将格式化字符串地址压入堆栈
0114183F call _printf (0114104Bh) ;调用printf函数
01141844 add esp,8 ;恢复堆栈平衡
上述反汇编代码中,第一个push语句将加法表达式11+22的和入栈,和由编译器在编译时计算出结果。第二个push语句将格式化字符串偏移地址(01147B30h)入栈。call指令调用C语言标准库函数printf输出结果。最后一条语句add esp,8恢复堆栈平衡。
结论
1.一条C语句printf("%d", 11 + 22);对应多条汇编语句。
2.C语言采用堆栈传参方式,将实参21H和格式化字符串偏移地址入栈。
3.C语言的函数调用约定为从右往左的顺序入栈,先push第一个实参33,然后再push第二个实参。
4.C语言的函数由调用者负责堆栈平衡。push两个实参入栈,每个实参占用4个字节,一共占用8个字节,执行call指令时,压入32位返回地址,执行ret指令时,将返回地址pop到指令指针寄存器eip中。实参占用的8个字节由add esp,8释放,至此全部堆栈空间已被释放。堆栈空间如图2-1所示。
图2-1 示例二堆栈框架
实验四:计算整数的差并显示结果
VS新建项目2-1-2.c:
/*
显示整数11和22的差
*/
#include <stdio.h>//C标准库输入输出头文件
int main(void)//函数头
{
printf("%d", 11 - 22); //%d用十进制数显示整数11-22的差
return 0;//结束返回
}
●输出结果:-11。
练习
1、请读者将2-1-2.c翻译成汇编语言实现。
2、请读者分析2-1-2.c的反汇编代码。
实验五:格式化字符串和转换说明
VS新建项目2-1-3.c:
/*
人性化的显示整数11和22的和
*/
#include <stdio.h>//C标准库输入输出头文件
int main(void)//函数头
{
//%d用十进制数显示整数11+22的和
printf("15与22的和是%d。\n", 11 + 22);//'\n'是换行符,ASCII码值是0AH
return 0;//结束返回
}
●输出结果:15与22的和是33。
练习
1、请读者将2-1-3.c翻译成汇编语言实现。注意:需要将Notepad++字符集设置为中文简体,“编码”>“编码字符集”>“中文”>“GB2312中文简体”。
2、请读者分析2-1-3.c的反汇编代码。
实验六:分行显示一
VS新建项目2-1-4.c:
/*
打招呼并进行自我介绍
*/
#include <stdio.h>//C标准库输入输出头文件
int main(void)//函数头
{
printf("您好!我叫编程达人。\n");//'\n'是换行符,ASCII码值是0AH
return 0;//结束返回
}
●输出结果:您好!我叫编程达人。
实验七:分行显示二
VS新建项目2-1-5.c:
/*
打招呼并进行自我介绍
*/
#include <stdio.h>//C标准库输入输出头文件
int main(void)//函数头
{
printf("您好!\n我叫编程达人。\n");//两个换行符
return 0;//结束返回
}
●输出结果:
您好!
我叫编程达人。
实验八:分行显示三
VS新建项目2-1-6.c:
/*
打招呼并进行自我介绍
*/
#include <stdio.h>//C标准库输入输出头文件
int main(void)//函数头
{
printf("您好!\n");
printf("我叫编程达人。\n");
return 0;//结束返回
}
●输出结果:
您好!
我叫编程达人。
实验九:响铃
VS新建项目2-1-7.c:
/*
打招呼并3次响铃
*/
#include <stdio.h>//C标准库输入输出头文件
int main(void)//函数头
{
printf("您好!\a\a\a\n");//转义字符'\a'响铃
return 0;//结束返回
}
●输出结果:您好!+3次响铃。
结论
1.在实验六、实验七和实验八中,我们可以在格式化常量字符串中灵活使用换行符’\n’实现换行,也可以多次调用printf函数实现换行输出。可以根据实际需要采用不同的方案。如果为了增加代码的可读性,方便注释,可以采用实验八的方案。
2.在实验九中,我们通过输出转义字符’\a’向屏幕输出响铃。
3.在C语言中,类似’\n’换行符和’\a’响铃这样的特殊符号称为转义字符,后面的章节中我们会接触更多的转义字符。
4.字符串常量,又称字面量,定义在全局常量区。像"ABC"和"您好!"这样用双引号括起来的一连串的文字称为字符串常量。在代码中,我们通过字符串常量在全局常量区的偏移地址引用字符串常量。当我们引用常量字符串时,其实引用的是它的地址,即指针。
练习
1、请读者将实验六、实验七、实验八和实验九的C语言代码翻译成汇编语言实现。
2、请读者分析2-1-4.c、2-1-5.c、2-1-6.c、2-1-7.c的反汇编代码。
■C语言中的符号
C语言通过大量的符号来简化代码。C语言符号的命名如表2-1所示。
+ 加号、正号、加 | { 左大括号 |
- 减号、负号、连字符、减 | } 右大括号 |
* 星号、乘号、米号、星 | [ 左方括号、左中括号 |
/ 斜线、除号 | ] 右方括号、右中括号 |
\ 反斜线 | < 小于 |
¥ 货币符号 | > 大于 |
% 百分号 | ? 问号 |
. 点 | ! 感叹号 |
, 逗号 | & and符 |
: 冒号 | ~ 波浪线 |
; 分号 | ^ 音调符号 |
‘ 单引号 | # 井号 |
“ 双引号 | _ 下划线 |
( 左括号、左圆括号、左小括号 | = 等号、等于 |
) 右括号、右圆括号、右小括号 | | 竖线 |
表2-1 C语言中的符号
本文摘自编程达人系列教材《汇编的角度——C语言》。