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

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文件,但内容有所不同。

伪指令主要包括:

  1. 宏定义指令,如#define Name TokenString,#undef以及编译器内建的一些宏,如__DATE__, __FILE__等。
  2. 条件编译指令,如#ifdes,#ifndef,#else,#elif,#endif等。
  3. 头文件包含指令,如#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. 动态链接

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

对上述四点进行简要的总结

  1. 预处理:参数“-E”gcc -E hello.c -o hello.i
  2. 编译:参数"-S"gcc -S hello.i > hello.s
  3. 汇编:参数"-c"gcc -c hello.s -o hello.o
  4. 链接:动态链接: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常见编译选项

  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. GCC编译C语言程序的步骤及每一步的主要工作: (1)预处理:处理源代码中的预处理指令(如#include、宏定义等),生成一个新的C程序。 (2)编译:将预处理后的C程序编译成汇编代码。 (3)汇编:将汇编代码转换成机器码。 (4)链接:将编译后的目标文件与库文件进行链接,生成最终的可执行文件。 2. ELF格式文件与BIN格式文件的主要区别是什么? ELF(Executable and Linkable Format)和BIN(Binary)是两种不同的可执行文件格式。主要区别如下: (1)内容:ELF文件包含程序代码、数据和符号表等信息,而BIN文件只包含程序代码。 (2)可读性:ELF文件是一种结构化的可执行文件格式,可以被动态链接器和调试器等工具解析和处理,因此具有一定的可读性;而BIN文件是一种简单的二进制文件格式,只包含机器指令,不具备可读性。 (3)兼容性:ELF文件通常用于Linux和其他类Unix系统,而BIN文件则通常用于MS-DOS和Windows系统。 总之,ELF文件比BIN文件更加灵活、可读性更高,但也需要更多的系统资源来解析和处理。 3. 交叉编译工具链中strip及objdump工具的主要用途是什么? strip工具用于从可执行文件中删除符号表和调试信息等,以减小文件体积和保护程序代码,可以提高程序的安全性。 objdump工具用于查看可执行文件或者目标文件的汇编代码、符号表、段信息等,可以用于调试和分析程序

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

园园顺顺崽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值