深入理解计算机系统:程序的机器级表示(2)

本章基于两种相关的机器语言:Intel IA32和x86-64。前者是32位计算机上的语言,后者是前者在64位机器上的拓展。
32位计算机的虚拟内存有4GB,而64位计算机高达256TB。

程序编码

假设一个C程序有两个文件p1.c和p2.c,在32位计算机上用Unix命令行编译这些代码如下:

unix> gcc -01 -o p p1.c p2.c
   
   
  • 1

命令gcc就是Linux上的默认编译器:GCC C。编译选项-01告诉编译器使用第一级优化。
gcc命令调用了一系列程序,将源代码转化成可执行代码。

1. 预处理

首先,C预处理器拓展源代码,插入所有用#include 命令指定的文件,并拓展所有用#define声明指定的宏。

2. 编译

然后,编译器产生两个源代码的汇编代码,名字分别是p1.s和p2.s。

3. 汇编

接下来,汇编语言将汇编代码转换成二进制目标代码,文件名为p1.o和p2.o。目标代码是机器代码的一种形式,它包含所有指令的二进制表示,但是还没有填入地址的全局值。

4. 链接

最后,链接器将两个目标代码文件与实现库函数(例如printf)的代码合并,并产生最终的可执行代码文件p。

IA32机器代码和原始的C代码差别非常大,一些通常对C语言程序员隐藏的处理器状态是可见的:程序计数器(在IA32中用%eip表示)、整数寄存器文件、条件码寄存器等。

在命令行上使用“-S”选项,就能得到C语言编译器产生的汇编代码:

unix> gcc -01 -S code.c
   
   
  • 1

这会使GCC运行编译器,产生一个汇编文件code.s,但是不做其他进一步的工作。

如果我们使用“-c”命令行选项,GCC会编译并汇编该代码:

unix> gcc -01 -c code.c
   
   
  • 1

这就会产生目标代码文件code.o,它是二进制格式,所以无法直接查看。机器实际执行的程序只是对一系列指令进行编码的字节序列,机器对产生这些指令的源代码一无所知。
要查看目标代码文件的内容,最有价值的是反汇编器,它是基于机器代码文件中的字节序列来确定汇编代码。

生成一个实际可执行的代码需要对一组目标文件运行链接器,而这组目标代码文件中必须含有一个main函数。假设在文件main.c中有下面这样的函数:

int main() {
    return sum(1,3);
}
   
   
  • 1
  • 2
  • 3

然后,用如下方法生成可执行文件prog:

unix> gcc -01 -o prog code.o main.c
   
   
  • 1

生成的prog不仅包含两个过程的代码,还包含了用来启动和终止程序的信息,以及用来与操作系统交互的信息。

控制

循环

C语言提供了多种循环结构,即do-while、while和for。汇编中用条件测试和跳转结合起来实现。大多数汇编器根据一个循环的do-while形式来产生循环代码,即使do-while的形式用得相对较少。其他的循环会首先转换成do-while形式,然后再编译成机器代码。首先从do-while开始。

do-while循环

do-while的通用形式可以翻译成如下所示的条件和goto语句:

loop:
    body-statement
    t=test-expr;
    if(t)
        goto loop;
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

每次循环,程序会执行循环体内的语句,然后执行测试表达式。如果测试为真,则回去再执行一次循环。

while循环

将while循环翻译成机器代码有很多种方法,常见的是使用条件分支,在需要时省略循环体的第一次执行,从而将代码转换成do-while循环,如下:

if(!test-expr)
    goto done;
do
    body-statement
    while (test-expr);
done:
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接下来,这个代码可以直接翻译成goto代码,如下:

    t=test-expr;
    if(!t)
        goto done;
loop:
    body-statement
    t=test-expr;
    if(t)
        goto loop;
done:
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

for循环

for循环可以很容易转换成while循环,进而转换成do-while形式:

init-expr;
if(!test-expr)
    goto done;
do {
    body-statement
    update-expr;
} while (test-expr);
done:
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

然后,将它转换成goto代码:

    init-expr;
    t=test-expr;
    if(!t)
        goto done;
loop:
    body-statement
    update-expr;
    t=test-expr;
    if(t)
        goto loop;
done:
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
                    <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
                        </div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值