前言
以<深入理解计算机系统>(以下称“本书”)内容为基础,对程序的整个过程进行梳理。本书内容对整个计算机系统做了系统性导引,每部分内容都是单独的一门课.学习深度根据自己需要来定
引入
本书第三章:程序的机器级表示内容的理解,这一章内容以汇编语言为主.汇编语言偏底层,用于系统级别的程序编写.如果不是做系统的,可以不用深入学习.理解汇编语言的关键是理解gcc指令,数据,寄存器,地址等概念.这些概念在C语言中也是很重要的,而且从汇编语言的角度会发现更多细节.
这章可以作为学习C语言的辅助,明白C语言的运行机制.其中重点关注能优化代码的部分.
本帖内容为3.1~3.3节,3.1节属于CPU的发展历程,了解即可.
3.2程序编码
一.gcc命令和代码转换
1>gcc的概念
本书P113
linux> gcc -Og -o p p1.c p2.c
gcc:GCC C编译器
-Og:编译优化等级.g表示较低优化等级,符合原始C代码整体结构方便学习.
实际使用-O1或-O2提高优化等级
-o:运行链接器,生成可执行文件
p:生成的可执行文件名.
整个语句的意思是:把文件p1.c和p2.c转换成可执行文件p.
实际上gcc命令调用了一整套程序,将源代码转化成可执行代码(黑体字是本书原话)
2>从C文件到机器代码转换过程
如下图所示:
左起是C语言代码,预处理成为后缀为i的文件,经编译器编译后成为.s文件.经汇编器成为.o文件(目标代码),他是机器代码的一种形式,经链接器链接库函数后,成为可执行文件,他是机器代码的第二种形式.目标代码和可执行文件这两种机器代码是十六进制数,无法直接阅读.学习内容是查看.s文件,即汇编代码.
转换过程中出现了iso(国际标准化组织的缩写),比较好记.
3>机器级编程的抽象
本书P113页3.2.1下面第一段讲了两个非常重要的概念:指令集架构和虚拟地址.
虚拟地址:提供了对物理地址的抽象.程序员面对的是一个巨大的连续的内存空间,是虚拟地址,而虚拟地址对应的实际硬件地址不是连续的,需要作转换.
指令集架构:代码的运行,在机器(芯片)层面上,体现为(许多)晶体管的开(1)和关(0)的.(注:1和0也可能反过来,由晶体管性质(pnp)或者(npn)决定).指令提供了描述机器运动的抽象.程序员通过指令来控制数据的变化,并以此表达逻辑.指令集架构是一套指令的规范.
不同芯片厂家使用的指令集架构可能不一样.所以指令集也可以看作是对芯片的抽象.笔者认为不同的指令集架构差别并不大,就算差别大,也不会影响程序员使用,他们设计的原则都是方便程序员编写代码.
4>gcc生成各种代码的指令
1.生成汇编代码:-S
示例:本书P114
linux> gcc -Og -S mstore.c
结果:生成mstore.s文件---汇编代码,程序员可以读懂的
2.生成目标代码:-c
示例:本书P115
linux> gcc -Og -c mstore.c
结果:生成mstore.o文件---目标代码
3.生成可执行文件:-o
示例:本书P113
linux> gcc -Og -o p p1.c p2.c
结果:生成p---可执行文件.
生成实际可执行代码需要对一组目标代码文件运行链接器(黑体字是原话),其中某个文件(.c)必须有main函数,原因是提供程序入口.
从上面可以看出,gcc指令不是"线性"工作的,可以根据需要生成gcc支持格式的代码
5>反汇编器:objdump -d
机器代码(包括目标代码和可执行代码)是一连串的十六进制数,不方便阅读,因此需要将其转换为较为符合阅读习惯的汇编代码 .反汇编器可以用在目标代码和可执行代码上
示例:本书P115---反汇编目标代码
linux> objdump -d mstore.o
反汇编可执行文件
linux> objdump -d p //和上面生成可执行文件的代码对应
二.汇编代码格式
本书汇编代码格式采用ATT(贝尔实验室AT&T),此外还有Intel格式,了解即可.
三.为什么使用汇编代码和汇编代码的写法
本书P118方框内容"把C程序和汇编代码结合起来"说了使用汇编代码的原因:访问C程序访问不到的机器的低层特性.举了个例子:条件码标志的访问.
可以这样理解:C程序封装了汇编代码,但某些在汇编代码层面的数据,没有给C程序提供接口,因此需要写汇编代码来作数据访问.
汇编代码的写法有两种:第一种方法是汇编代码编写整个函数,在链接阶段把他们和C函数组合起来.第二种方法是利用GCC的支持,直接在C程序中嵌入汇编代码.很显然第二种方法是更容易被接受的.本书P118倒数第二段:用asm伪指令可以在C程序种包含简短的汇编代码(黑体字是原话),具体写法笔者也不知道,所以不展开,用到的时候再查资料.
本书P119第一段做了说明:C程序中包含的汇编代码与某类机器相关,所以只应该在想要的特性只能以此种方式才能访问到时才使用它.(黑体字是原话)
这段话笔者解读为两层含义:
一是使用汇编代码的情况不多.站在C的角度,编写代码的原则必定是和机器无关.(和Java的跨平台一样,写代码不用去考虑在什么平台运行,C写出来的程序面对的始终是gcc翻译),以此推导如果要用到汇编代码的时候,那就要访问机器的底层.而此时使用的指令集架构不同,数据的表示方法或许不同.以上面的条件码标志访问为例,可以需要区分X86架构或者RISC架构的表示
二是在上面那段话描述的情况下,代码想要通用(一次编写多处使用)需要分支结构
//伪代码
if(expression_A){ //expression_A:架构A在C语言中的表示
asm:
汇编代码;
}
else if(expression_B){ //expression_B:架构B在C语言中的表示
asm:
汇编代码;
}
说明:因为目前对架构的了解不多,上述属于笔者个人解读,未必如此.
3.3数据格式
这一节的内容就在这张图里,基本上也是耳熟能详的,比较好记.
数据后缀说明:
b(字节) w(字/2字节) l(双字/4字节) q(四字/8字节)
指针的长度等于计算机字长,64位机器的字长为8字节.所有指针(不光是图中的char*)长度都是8字节,这点也可以在程序中用sizeof(int*)---(或者其他类型指针,这里用了int*)来查看.