C程序编译过程与ELF可执行文件及gcc常见编译选项

您所在的位置:网站首页 编译程序直接生成可执行文件是对的吗 C程序编译过程与ELF可执行文件及gcc常见编译选项

C程序编译过程与ELF可执行文件及gcc常见编译选项

2024-07-01 00:55| 来源: 网络整理| 查看: 265

C程序编译过程与LEF可执行文件及gcc常见编译选项

以C语言版的Helllo World 为例 通常使用gcc来生成可执行文件,命令: gcc hello.c 默认生成可执行文件a.out. 而编译命令gcc hell0.c 实际上可以分解为4个步骤:

预处理(Preprocessing)编译(Compilation)汇编(Assembly)链接(Linking) 故,gcc只是所有编译链接的前端工具,后端会调用其他的命令 gcc编译过程 头文件的作用:

存放函数的声明(此处没有函数的定义);比如printf函数,其声明在stdio.h头文件中,而其函数定义代码则在libc.so动态库中

1.预处理(Preprocessing)

预处理是读取c源程序,对其中的伪指令和特殊符号进行“替代”处理;经过此处理,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义和没有经过预处理的源文件是相同的,仍然是C文件,但内容有所不同。

伪指令主要包括: 宏定义指令,如#define Name TokenString,#undef以及编译器内建的一些宏,如__DATE__, __FILE__等。条件编译指令,如#ifdes,#ifndef,#else,#elif,#endif等。头文件包含指令,如#include "FileName"或者#include 等。 简单的说,伪指令就是#开头的指令。 预处理的主要过程: 将所有的#define删除,并且展开所有的宏定义处理所有的条件预编译指令,比如:#if #ifdef #else #endif等处理#include预编译指令,将被包含的文件插入到该预编译指令的位置删除所有注释添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号保留所有的#pragma编译器指令,因为编译器需要使用它们 预处理命令 gcc -E hello.c -o hello.i

上述命令可进行预处理,参数-E表示只进行预处理 同时,也可以用下述指令完成预处理过程,其中cpp是预处理器

cpp hello.c > hello.i

预处理后的结果hello.i还是C语言源代码,我们可以使用cat或vim命令查看hello.i中的代码。

2.编译(Compilation)

编译程序即通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。 优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化,这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。

编译生成汇编文件的命令 gcc -S hello.i > hello.s 交叉编译器

即在PC上写的代码编译生成的程序不在PC上跑,而是在开发板上跑 参考命令如下:

/opt/xtools/arm920t/bin/arm-linux-gcc -S hello.i > hello.s

此时,我们使用PC的编译器就会编译生成x86的汇编,而使用ARM的编译器则生成ARM的汇编文件。 同一份C代码不作任何修改,使用不同的编译器编译就会生成在不同机器上运行的程序,这就是C程序的可移植性。

3.汇编(Assembly)

汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。 目标文件由段组成。通常一个目标文件中至少有两个段:

代码段(文本段):该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写;数据段:主要存放程序中要用到的各种常量、全局变量、静态的数据。一般数据段都是可读,可写,可执行的; 汇编生成目标文件命令: gcc -c hello.s -o hello.o 4.链接(Linking)

汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。 链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体,也就是可执行程序。 根据开发人员指定的库函数的链接方式的不同,链接处理可分为两种: 1. 静态链接 2. 动态链接

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。 动态链接与静态链接生成的可执行文件大小对比 【补充:函数即文本段和数据段的入口地址是在链接的时候决定的】

对上述四点进行简要的总结 预处理:参数“-E”gcc -E hello.c -o hello.i编译:参数"-S"gcc -S hello.i > hello.s汇编:参数"-c"gcc -c hello.s -o hello.o链接:动态链接:gcc hello.c -o hello ; 静态链接:gcc hello.c -o hello -static ELF可执行文件格式

Linux/Unix的可执行文件以及动态库都是以ELF(Executable Linkage Format)存在的。在Linux下,可以使用readelf命令查看ELF文件,关于加载过程所需要的信息都在ELF文件头里面,可以用使用readefl filename -e来查看EFL文件所有的头。我们可以先来查看下hello.c编译出来的hello可执行文件的ELF头信息:

readelf 命令 dayu@dayu-virtual-machine:~/apue$ readelf hello -e ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Position-Independent Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x1060 Start of program headers: 64 (bytes into file) Start of section headers: 13976 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 13 Size of section headers: 64 (bytes) Number of section headers: 31 Section header string table index: 30 ... ... dayu@dayu-virtual-machine:~/apue$ readelf -d hello Dynamic section at offset 0x2dc8 contains 27 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000c (INIT) 0x1000 0x000000000000000d (FINI) 0x1188 0x0000000000000019 (INIT_ARRAY) 0x3db8 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) 0x000000000000001a (FINI_ARRAY) 0x3dc0 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) 0x000000006ffffef5 (GNU_HASH) 0x3b0 0x0000000000000005 (STRTAB) 0x480 0x0000000000000006 (SYMTAB) 0x3d8 0x000000000000000a (STRSZ) 141 (bytes) 0x000000000000000b (SYMENT) 24 (bytes) 0x0000000000000015 (DEBUG) 0x0 0x0000000000000003 (PLTGOT) 0x3fb8 0x0000000000000002 (PLTRELSZ) 24 (bytes) 0x0000000000000014 (PLTREL) RELA 0x0000000000000017 (JMPREL) 0x610 0x0000000000000007 (RELA) 0x550 0x0000000000000008 (RELASZ) 192 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes) 0x000000000000001e (FLAGS) BIND_NOW 0x000000006ffffffb (FLAGS_1) Flags: NOW PIE 0x000000006ffffffe (VERNEED) 0x520 0x000000006fffffff (VERNEEDNUM) 1 0x000000006ffffff0 (VERSYM) 0x50e 0x000000006ffffff9 (RELACOUNT) 3 0x0000000000000000 (NULL) 0x0 ldd 命令(查看动态链接的可执行文件)

ldd命令

gcc常见编译选项

gcc常见编译选项



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3