【CSAPP】<Chapter 3>
编译器承担了生成汇编代码的大部分工作,但是阅读和理解汇编代码仍然是重要能力。
学习意义:(1)理解编译器的优化能力,分析代码中隐含的低效率。
(2)高级语言提供的抽象层会隐藏我们想要了解的程序的运行时行为。
相对于C代码表示的计算操作,优化编译器能够(1)重新排列执行顺序,
(2)消除不必要计算,
(3)用快速操作替换慢速操作,
(4)甚至将递归变换成迭代。
(表述基于x84-64的机器语言。(32位前身是IA-32)(Intel处理器系列俗称x86))
计算机工业已经完成了从32位到64位的过渡。32位机器只能使用4GB的随机访问存储器。(每个后继处理器的设计都是向后兼容的。)
摩尔定律:晶体管数量每26个月就会翻一番。
使用Unix命令行编译文件p1.c和p2.c的代码:linux> gcc -Og -o p p1.c p2.c
“gcc”:GCC C编译器(linux上默认的编译器),可以简单地用“cc”来启动它。
编译选项“-Og”:说明编译器使用会生成符合原始C代码整体结构的机器代码优化等级。较高级别的优化:“-O1”,“-O2”。
“gcc”命令调用了一整套程序将源代码——>可执行代码:
(1)C预处理器,插入所有#include命令指定的文件,扩展所用#define声明指定的宏。
(2)编译器产生两个源文件的汇编代码p1.s和p2.s。
(3)编译器将汇编代码——>二进制目标代码文件p1.o和p2.o。(包含所有指令的二进制表示,还没有填入全局值的地址)
(4)链接器将两个目标代码文件与实现库函数的代码合并——>可执行代码文件p(由“-o p”指定)
机器级编程中最重要的两种系统抽象:
(1)指令集体系结构ISA(Instruction Set Architecture)定义机器级程序的格式,行为。(处理器状态,指令格式,每条指令对状态的影响)
(2)虚拟地址。使得提供的内存模型看上去是一个很大很大的字节数组。
机器代码对C语言程序员隐藏的处理器状态:
(1)程序计数器PC(%rip):给出将要执行的下一条指令在内存中的地址。
(2)整数寄存器文件:包含16个命名的位置,分别存储64位的值,(存储地址,整数数据),记录状态,保存临时数据。
(3)一组向量寄存器:存放多个整数/浮点数值。
(汇编代码不区分有无符号整数,不同类型指针。)
程序内存包括:
(1)程序的可执行机器代码。
(2)操作系统需要的一些信息,用来管理过程调用和返回的运行时栈。
(3)用户分配的内存块(如malloc库函数分配的)。
x86-64的虚拟地址是由64位,目前地址的高16位必须设置为0。所以实际地址范围为(0 ~ 64TB)。
“-S”选项让GCC运行编译器,只产生汇编文件,不做进一步的工作:linux> gcc -Og -S mstore.c
“-c”选项让GCC编译并汇编该代码: linux> gcc -Og -c mstore.c
展示程序的字节级(二进制目标代码)表示:
用反汇编器确定该过程的代码长度为14字节。在mstore.c上运行GNU调试工具GDB:linux> x/14xb multstore
让GDB(显示为“x”)从函数multstore所处地址开始的14个十六进制格式表示(简写为“x”)的字节(简写为“b”)
查看机器代码文件的内容:
使用反汇编器(根据机器代码产生的一种类似于汇编代码的格式):linux> objdump -d mstore.o(调用程序OBJDUMP,即object dump)
机器代码特性:
(1)指令长度从1 ~ 15不等。常用指令/操作数少的指令所需字节数少,反之则多。
(2)设计指令格式的方式:从某个给定位置起,可以将字节码唯一地解码成机器指令。(如只有pushq %rbx以字节53开头)(类似哈弗曼编码)
(3)只基于机器代码文件中的字节序列来确定汇编代码。不需要访问改程序的源代码/汇编代码。
(4)反汇编代码与汇编代码指令命令规则差别:省略了很多指令结尾的“q”,给“call”和“ret”指令添加了“q”后缀。
生成实际可执行的代码需要对一组目标代码文件(必须含有一个main函数)运行链接器。
执行可执行文件prog:linux> gcc -Og -o prog main.c mstore.c
有一部分代码与mstore.c反汇编产生的代码极为相似。不同之处:
(1)链接器将这段代码的地址移到了一段不同的地址范围中。
(2)链接器填上了callq指令调用函数mult2需要使用的地址。(链接器任务之一:为函数调用找到匹配的函数的可执行代码位置。)
(3)多了两行代码:插入它们使得函数代码变为16字节,以便更好地放置下一个代码块。(就存储器系统性能而言)
对于一些应用程序,程序员必须用汇编代码来访问机器的低级特性
(1)用汇编语言编写完整函数,放入独立的汇编代码文件中,让汇编器和编译器把它和C语言书写的代码合并起来。
(2)使用GCC内联汇编特性,用asm伪指令可以在C语言中包含简短的汇编代码。
C语言编译器无法访问到的机器特性举例:
x86-64处理器执行算术/逻辑运算时,如果运算结果的低8位中有偶数个1,则一名为“PF”(parity flag)的条件码值设为1;
在C语言中,至少需要7次移位,掩码,异或运算。
C声明 Intel数据类型 汇编代码后缀 字节数
char 字节 b 1
short 字 w 2
int 双字 l 4
long 四字 q 8
char* 四字 q 8
float 单精度 s 4
double 双精度 l 8 int和double的“l”后缀不会混淆,因为浮点数使用一组完全不同的指令和寄存器。