上QQ阅读APP看书,第一时间看更新
1.2.1 从源代码到目标代码
一个简单的C示例如下:
int global_count = 10; int add(int i, int j){ return i + j; } main(){ int i = 3; int j = 5; int result = add(i, j); }
该示例非常简单,不存在动态链接,编译、链接完成后即可在OS中执行。但是程序要在OS上执行,需要符合OS的执行要求,主要包括:
- 产生的可执行文件必须符合格式规范(如Linux中必须符合ELF格式)。
- 可执行文件中内容的组织符合OS执行程序的约定规范,例如程序在执行时由数据段(data segment)、代码段(text segment)等组成。
以Linux系统为例,上面的源代码和编译生成的可执行文件(ELF格式)的对应关系如下图1-3所示。
图1-3 代码和ELF格式约定
以Linux/X86-64为例,通过gcc编译器对上述代码进行编译,产生目标文件。文件格式为ELF,可以使用objdump命令(或readelf命令)对编译后的目标文件进行解析。首先可以确认一下数据段的信息,如下所示:
Disassembly of section .data:
0000000000600868 <__data_start>:
600868: 00 00 add %al,(%rax)
……
000000000060086c <global_count>:
60086c: 0a 00 or (%rax),%al
在这个数据段中有两个变量__data_start和global_count,其中global_count是代码中定义的全局变量,可以看到该变量占用的空间为4字节,初始值为10;而__data_start是gcc在链接时创建的一个全局变量,该变量指向数据段开始的位置,该变量的大小也是4字节。
编译器除了满足OS对于可执行文件的约定规范外,其中一个重要的功能就是针对代码进行编译优化(当然也包含了内存数据的布局等)。接下来看一下代码段的内容。代码段非常长,这里只关注add函数的汇编代码,如下所示:
0000000000400474 <add>:
400474: 55 push %rbp
400475: 48 89 e5 mov %rsp,%rbp
400478: 89 7d fc mov %edi,-0x4(%rbp)
40047b: 89 75 f8 mov %esi,-0x8(%rbp)
40047e: 8b 45 f8 mov -0x8(%rbp),%eax
400481: 8b 55 fc mov -0x4(%rbp),%edx
400484: 8d 04 02 lea (%rdx,%rax,1),%eax
400487: c9 leaveq
400488: c3 retq
注意
在gcc编译过程中采用的是默认编译优化级别(默认编译优化级别为O0),如果采用不同的编译优化级别,生成的代码会略有不同。
在C/C++中,编译优化体现在源代码的编译时间长短不同,同时不同的编译代码执行效率也会不同。在JVM的执行过程中也存在同样的问题,并且因为JVM在编译代码执行过程中需要先等待编译代码完成后才能执行,所以编译时长会直接影响应用执行的性能。