编译器承担了生成汇编代码的大部分工作,但是阅读和理解汇编代码仍然是重要能力。
学习意义:(1)理解编译器的优化能力,分析代码中隐含的低效率。(2)高级语言提供的抽象层会隐藏我们想要了解的程序的运行时行为。e
相对于C代码表示的计算操作,优化编译器能够重新排列执行顺序,消除不必要计算,用快速操作替换慢速操作,甚至将递归变换成迭代。
表述基于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