3.2 Program Encodings 程序编码
摘要:
1.在IA32的Unix机器上,调用Unix指令 3.2 unix> gcc -O1 -o p p1.c p2.c 01表示一级优化,优化越高,编译用时越长(这不废话吗),并且调试起来更麻烦,因为错误的地方和源代码对应起来更难。所以-02级优化更推荐。
2.编译步骤:
<1.调用C预处理器(C preprocessor),扩展代码(把所有#include标示的文件扩展进来),扩展宏(把所有#define标示的变量展开包含进来)。
<2.编译器(compiler)生成汇编码(assemblycode),p1.s 、 p2.s两个文件,为什么是两个呢,笔者(现在都是用键盘,还是用键者合适- ,-)很好奇。
<3.汇编器(assembler) 把2个汇编文件转化成二进制目标代码(binary object-code),p1.o、p2.o。Object code是机器代码的一种形式,它包含所有指令的二进制表示(可以打开看下,都是10010100101,如果是用16进制打开就不同了),它比第二步骤产生的汇编语言更接近机器码(又在废话- -),但是这种形式没有填入全局值的地址(fill the adress of global values).合理没有填全局值的地址,笔者想是因为要merge一些链接库吧,地址还不确定。
<4.链接器(linker)将这两个目标代码文件与实现函数的库( library of functions)的文件合并(合并即Merge),并生成最终可执行代码(Executable code) p.p文件里面的code是处理器(processor)真正能执行的代码格式。
(正当笔者对着写code迷糊的时候,作者说第七章详细介绍它们,可以放心了).
分述:
1. Machine-Level Code 机器级代码
<1.计算机使用了多种形式的抽象(Abstraction)来隐藏实现细节,对于机器级编程(Machine level programming)来说,两个最重要的抽象:
a.机器级程序的格式和行为(the format and behavior of a machine-level program),定义为指令集体系结构(instruction set architecture, ISA)。
ISA 定义了处理器状态(processor state),指令的格式(format of the instructions),一级每条指令对状态的影响(the effect each of these instructions will have on the state),每个ISA(如IA32,x36-64)将程序的行为描述为按顺序执行,一条执行结束,下一条执行开始,其实现代的处理器硬件远比描述的精细复杂,并发执行,但是采取了某种安全措施让他们工作起来和ISA指定的顺序完全一致。
b.机器级程序对硬件存储器地址的抽象,机器级程序使用的地址是虚拟地址,虚拟地址提供一个地址模型,看起来像一个巨大的字节(byte)数组。实际上是由多个硬件存储器和操作系统软件(虚拟内存?)组合而成,将在第9章介绍.
<2.汇编很接近机器代码,并具有很好的可读性,它和C语言的差距很大,所以理解汇编代码以及它和C语言的联系,对理解计算机如何执行程序非常重要,而且以下处理器状态C语言是不可见,但汇编是可见的:
a.程序计数器(program counter),通常被称为PC,IA32中用%eip表示。指向下一条将要执行的指令的地址。
b.整数寄存器文件(integer register file),它包含8个命名的位置(named location),用来存储32位的值。他们可以用来保存 存储地址(adreess,对应于C语言的指针),也可以保存整数数据。作用是:其中一部分寄存器是用来记录程序中比较重要的状态,其他的用来保存临时数据(如过程procedure中的局部变量和函数function.的返回值)。
c.条件寄存器(condition code registers)保存着最近执行的算术或逻辑指令的状态信息(Status),他们用来实现控制(control)或数据流(data flow)中的条件变化。常用来实现if和while。
d.一组浮点寄存器(A set of floating-point registers)用来存储浮点数据。
<3 .C语言中的各种类型,数组,结构,指针,有符号数,无符号数,在汇编代码中都是字节或一组字节来表示,也不区分指针和整数。
<4. 程序存储器 (program memory)(这个是的单片机的cpu才有吧,用来保证断电后程序仍在,笔者读到这莫名其妙),它包含:程序的可执行代码,操作系统需要的一些信息,一个运行时栈(runtime stack,用来管理过程的调用和返回),存储器块(Memory Blocks,主要由用户分配,如用malloc函数)。程序存储器只用虚拟内存寻址,而且任何一个给定时刻,只有一部分地址是被认为是合法的(如IA32虽然能识别4gb内存,但在某一时刻,只有其中的几M被认为是合法的),操作系统负责把虚拟内存地址翻译成实际内存地址。
<5.一条机器指令只执行一个非常基本的操作。如:把两个寄存器的值相加,传递内存中的数据到寄存器,或者根据条件分支到一个新的指令地址地址(中文的翻译原版烂透了,原文是:conditionally branch to a new instruction address,译文是:条件分支转移到新的地址。动词变名词有木有?)。
2.代码示例
<1.编译时用unix> gcc -O1 -S code.c中的-s选项就可以得到汇编代码,编译器编译到汇编代码,不做进一步的操作。还有-c就会得到目标代码文件。
<2.C代码:
1 int accum = 0; 2 3 int sum(int x, int y) 4 { 5 int t = x + y; 6 accum += t; 7 return t; 8 }
对应汇编代码:
sum: pushl %ebp movl %esp, %ebp movl 12(%ebp), %eax addl 8(%ebp), %eax addl %eax, accum popl %ebp ret
pushl %ebp --把%ebp的内容入栈,
movl %esp, %ebp --把%esp的内容复制到%ebp
movl 12(%ebp), %eax --把%ebp偏移12的位置的内存中的内容复制到%eax
addl 8(%ebp), %eax --把%ebp的值+8的位置的值加上%eax的值,放进%eax
addl %eax ,accum --把%eax的值+进accum
popl %ebp --把%ebp的值弹出栈
ret --返回
上面的代码,除了sum:每行都对应一条机器指令。accum是个对全局变量的引用,这个在上节提到过,这是因为全局变量的存储位置现在编译器还没有确定。
unix> gcc -O1 -c code.c 产生的机器码:
55 89 e5 8b 45 0c 03 45 08 01 05 00 00 00 00 5d c3