Linux中C语言程序编译过程
Linux中C语言程序编译过程
如下内容参考链接为:https://www.cnblogs.com/wangshaowei/p/11279838.html
https://blog.csdn.net/chun_1959/article/details/43731937
在此对作者的付出表示感谢!
程序编译就是要将高级语言如C语言转化成计算机能识别的二进制文件的过程,所以我们一般分为如下四个步骤
预处理(Preprocessing)
编译(Compilation)
汇编(Assembly)
链接(Linking)
大体框图如下:
下来我们就分别对这几个部分进行介绍
1.预处理
预处理的过程主要包括以下过程:
将所有的#define删除,并且展开所有的宏定义,并且处理所有的条件预编译指令,比如#if #ifdef #elif #else#endif等。
处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
删除所有注释“//”和“/* */”。
添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
保留所有的#pragma编译器指令,后续编译过程需要使用它们。
使用gcc进行预处理的命令如下:
gcc -E 表示执行完预处理就停止执行,生成.i 文件。
gcc -E hello.c -o hello.i
执行结果
C语言源程序(hello.c)
#include
#define NUM 10
//本程序用来打印一个Hello,World!
int main()
{
int a = NUM;
printf("Hello,World!\n");
printf("a = %d\n",a);
return 0;
}
C语言预处理程序(hello.i)
...
extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 913 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 943 "/usr/include/stdio.h" 3 4
# 9 "hello.c" 2
int main()
{
int a = 10;
printf("Hello,World!\n");
printf("a = %d\n",a);
return 0;
}
通过实际操作我们发现在预处理过程中符合如上预处理的过程。
2.编译
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。
GCC的选项-S使GCC在执行完编译后停止,生成汇编程序。
gcc -S hello.i -o hello.s
生成hello.s
.file"hello.c"
.section.rodata
.LC0:
.string"Hello,World!"
.LC1:
.string"a = %d\n"
.text
.globlmain
.typemain, @function
main:
.LFB0:
.cfi_startproc
pushl%ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl%esp, %ebp
.cfi_def_cfa_register 5
andl$-16, %esp
subl$32, %esp
movl$10, 28(%esp)
movl$.LC0, (%esp)
callputs
movl28(%esp), %eax
movl%eax, 4(%esp)
movl$.LC1, (%esp)
callprintf
movl$0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.sizemain, .-main
.ident"GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section.note.GNU-stack,"",@progbits
3.汇编
汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编相对于编译过程比较简单,通过调用Binutils中的汇编器as根据汇编指令和处理器指令的对照表一一翻译即可。
Binutils
一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。这一组工具是开发和调试不可缺少的工具,分别简介如下:
addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。
as:主要用于汇编,有关汇编的详细介绍请参见后文。
ld:主要用于链接,有关链接的详细介绍请参见后文。
ar:主要用于创建静态库。
ldd:可以用于查看一个可执行程序依赖的共享库。
objcopy:将一种对象文件翻译成另一种格式,譬如将.bin转换成.elf、或者将.elf转换成.bin等。
objdump:主要的作用是反汇编。有关反汇编的详细介绍,请参见后文。
readelf:显示有关ELF文件的信息,请参见后文了解更多信息。
size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等,请参见后文了解使用size的具体使用实例。
为了便于初学者理解,在此介绍动态库与静态库的概念:
如果要将多个.o目标文件生成一个库文件,则存在两种类型的库,一种是静态库,另一种是动态库。
在windows中静态库是以 .lib 为后缀的文件,共享库是以 .dll
为后缀的文件。在linux中静态库是以.a为后缀的文件,共享库是以.so为后缀的文件。
静态库和动态库的不同点在于代码被载入的时刻不同。静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。在Linux系统中,可以用ldd命令查看一个可执行程序依赖的共享库。
如果一个系统中存在多个需要同时运行的程序且这些程序之间存在共享库,那么采用动态库的形式将更节省内存。
当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o目标文件后,才能进入下一步的链接工作。
注意:
目标文件已经是最终程序的某一部分了,但是在链接之前还不能执行。
hello.o目标文件为ELF(Executable and Linkable Format)格式的可重定向文件。
CC的选项-c使GCC在执行完汇编后停止,生成目标文件
gcc -c hello.s -o hello.o
hello.o
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE# objdump -h hello.o
hello.o: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000038 00000000 00000000 00000034 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 0000006c 2**0
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 0000006c 2**0
ALLOC
3 .rodata 00000015 00000000 00000000 0000006c 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 00000025 00000000 00000000 00000081 2**0
CONTENTS, READONLY
5 .note.GNU-stack 00000000 00000000 00000000 000000a6 2**0
CONTENTS, READONLY
6 .eh_frame 00000038 00000000 00000000 000000a8 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE#
更多详情请参考:https://blog.csdn.net/gt1025814447/article/details/80442673
4.链接
链接也分为静态链接和动态链接,其要点如下:
静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链接库中)拷贝到最终的可执行程序中。为创建可执行文件。
链接器必须要完成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
在Linux系统中,gcc编译链接时的动态库搜索路径的顺序通常为:首先从gcc命令的参数-L指定的路径寻找;再从环境变量LIBRARY_PATH指定的路径寻址;再从默认路径/lib、/usr/lib、/usr/local/lib寻找。
在Linux系统中,执行二进制文件时的动态库搜索路径的顺序通常为:首先搜索编译目标代码时指定的动态库搜索路径;再从环境变量LD_LIBRARY_PATH指定的路径寻址;再从配置文件/etc/ld.so.conf中指定的动态库搜索路径;再从默认路径/lib、/usr/lib寻找。
在Linux系统中,可以用ldd命令查看一个可执行程序依赖的共享库。
4.1 使用动态库进行链接
Linux中使用指令
gcc hello.c -o hello
size hello //使用size查看大小
ldd hello //查看链接的动态库
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE# size hello
text data bss dec hexfilename
1198 284 4 1486 5cehello
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE# ldd hello
linux-gate.so.1 => (0xb7745000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb757f000)
/lib/ld-linux.so.2 (0xb7746000)
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE#
4.2 使用静态动态库链接
Linux中使用指令
gcc -static hello.c -o hello
size hello //使用size查看大小
ldd hello //查看链接的动态库
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE# gcc -static hello.c -o hello
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE# size hello
text data bss dec hexfilename
658346 4096 5020 667462 a2f46hello
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE# ldd hello
not a dynamic executable
[email protected]:/mnt/hgfs/winshare/C_Practice/C_BASE_PRACTICE#
1 .bin文件
.bin文件是linux下可执行文件,相当于windows下的.exe文件。bin文件是二进制文件,里面没有地址标记。
2. o文件
目标文件。相当于windows下的obj文件。
3. elf文件
elf文件可以在linux上运行,但不能在裸机下运行
4.hex文件
Intel hex 文件常用来保存单片机或其他处理器的目标程序代码。它保存物理程序存储区中的目标代码映象。一般的编程器都支持这种格式。 就是机器代码的十六进制形式,并且是用一定文件格式的ASCII码来表示。
ELF文件格式如下图所示,位于ELF Header和Section Header Table之间的都是段(Section)。一个典型的ELF文件包含下面几个段:
.text:已编译程序的指令代码段。
.rodata:ro代表read only,即只读数据(譬如常数const)。
.data:已初始化的C程序全局变量和静态局部变量。
.bss:未初始化的C程序全局变量和静态局部变量。
.debug:调试符号表,调试器用此段的信息帮助调试。
总结
可由elf文件转化为hex和bin两种文件,hex也可以直接转换为bin文件,但是bin要转化为hex文件必须要给定一个基地址。而hex和bin不能转化为elf文件,因为elf的信息量要大。Axf文件可以转化为bin文件,KEIL下可用以下命令fromelf -nodebug xx.axf -bin xx.bin即可。
Linux中C语言程序编译过程相关教程