Linux中GCC

GCC合集,后续介绍g++、gdb、静态库和动态库以及makefile、cmake


前言

介绍GCC(GNU Compiler Collection)编译器的编译过程。


一、GCC编译器

GCC 是 Linux 下的编译工具集,是「GNU Compiler Collection」的缩写,包含 gcc、g++ 等编译器。这个工具集不仅包含编译器,还包含其他工具集,例如 ar、nm 等。
GCC 工具集不仅能编译 C/C++ 语言,其他例如 Objective-C、Pascal、Fortran、Java、Ada 等语言均能进行编译。GCC 还可以根据不同的硬件平台进行编译,即能进行交叉编译,在 A 平台上编译 B 平台的程序,支持常见的 X86、ARM、PowerPC、mips 等,以及 Linux、Windows 等软件平台。

1.1安装

$ sudo apt-get update
$ sudo apt-get install gcc g++
$ sudo gcc --version
$ sudo g++ --version

版本查看

1.2 GCC 编译过程

创建一个hello.c文件为实例

#include <stdio.h>
#define Max 3

int main()
{
    int i;
    for(i=1; i<Max; i++)
    {
        printf("Hello world\n"); // output:"Hello world"
    }
    return 0;
}

在命令行中可以输入以下指令实现编译test.c,并运行

$ gcc hello.c -o hello
$./hello
$ mkdir ./test
$ gcc hello.c -o ./test/hello  #修改路径

-o:output 指定文件名及路径,默认输出当前路径

GCC编译器在对程序进行编译的时候,分为四个步骤
过程

预处理(Pre-Processing):

- 作用:展开头文件,宏替换,去掉注释行
- 结果:C程序,通常是以.i作为文件扩展名
- 方法:使用参数 -E 生成预处理后的 C 文件 必须使用 -o 将名字命名为 hello.i 命令如下
$ gcc -E test.c -o test.i -v

查看运行结果:(cc1)
在这里插入图片描述

注意:虽然cc1主要作用是将源文件生成汇编代码,但在预处理中,我们主要处理的是头文件、宏定义、注释。然而我们在下一步的编译过程依然会使用 cc1

查看代码(只展示了后面):
在这里插入图片描述

查看 main() 中的内容,宏定义的Max替换为了3 注释//output: Hello World 也被去掉了

编译(Compiling):

  • 作用:gcc 首先检查代码的规范性、语法错误等,并将代码编译成汇编语言。
  • 结果:以 .s 作为文件扩展名的汇编文件。
  • 方法:使用参数 -S 将 hello.i 转换成 hello.s
    $ gcc -S hello.i
    $ gcc -S hello.i -o hello.s -v
    
    查看运行结果:(cc1)
    在这里插入图片描述

汇编(Assembling):

  • 作用:把编译阶段生成的 .s 文件转化成目标文件

  • 结果:以 .o 结尾的二进制文件(.o文件,object)

  • 方法:使用参数 -c 编译汇编文件为二进制文件

    $ gcc -c hello.s
    $ gcc -c hello.s -o hello.o
    

    查看运行结果:as
    在这里插入图片描述

  • 链接(Linking):

  1. 静态链接:编译过程中,将静态库加入到可执行文件中
  2. 动态链接:程序执行时,从系统中将相应的动态库加到内存中去
  • Linux系统中,gcc编译链接时动态库搜索路径的顺序通常为:
    1)从gcc命令参数-L指定路径寻找;
    2)再从环境变量LIBRARY_PATH指定的路径寻址;
    3)再从默认路径/lib、/usr/lib、/usr/local/lib寻找。

  • Linux系统中,执行二进制文件时的动态库搜索路径的顺序通常为:
    1)先搜索编译目标代码时指定的动态库搜索路径;
    2)再从环境变量LD_LIBRARY_PATH指定的路径寻址;
    3)再从配置文件/etc/ld.so,sonf中指定的动态库搜索路径;
    4)再从默认路径/lib、/usr/lib寻找。

  • Linux系统下,可用ldd命令查看一个可执行程序依赖的动态库。

  • 作用:调用链接器将所有目标文件以及对程序需要调用的进行链接,得到可执行文件

  • 结果:以 .exe(.out) 作为文件扩展名的可执行文件

  • 方法:

    $ gcc hello.o -o hello -v   #链接动态库
    $ size hello 
    $ ldd hello # 动态库的相关
    

    查看运行结果:
    在这里插入图片描述
    这里再来看看,ELF可执行文件的大小以及链接的动态库:
    在这里插入图片描述

在主程序中调用了printf(),stdio.h中只有函数声明。并没有定义函数实现。系统把函数实现都做到了名为 libc.so.6 的库文件中。gcc在链接时会搜索 /usr/lib64 下查找,并链接到 libc.so.6 库函数中,这样就有 printf 的实现了。

注意:链接动态库和静态库有可能重合,所以,当有同名的静态库和动态库文件时,
gcc链接默认优先选择动态库,若要让链接选择静态库则需指定-static,强制性使用静态库链接。

使用以下代码,可以看到相对动态链接ELF文件大了许多:

	$ gcc -static hello.c -o hello # 链接静态库
	$ size hello  # 文件会变的很大
	$ ldd hello   # 说明没有动态库

在这里插入图片描述

扩展:ELF文件

简介:ELF(Executable Linkable Format)是可执行与可链接格式文件
作用:存储可执行文件、目标代码、共享库和核心转储文件
应用:Unix系统(Linux)和类Unix系统(FreeBSD)中
在这里插入图片描述

组成部分
  1. ELF头部(ELF Header):描述文件总体布局,包括文件类型、目标框架、入口地址等信息。
    查看方法:
	$ readelf -h hello

在这里插入图片描述

文心一言:(了解)
其中包含了关于名为“hello”的程序的信息。这些信息包括了程序的文件头、版本、类型和机器类型等。此外,还提到了该程序是用C语言编写的,并且是一个共享对象文件(.so)。程序使用的是X86-64架构的处理器。在输出中可以看到一些其他的信息,比如入口地址、开始的程序头部大小以及程序头部的大小等。

  1. 程序头部表(Program Header Table):描述文件中各个段(segment)的信息。
$ readelf -l hello

在这里插入图片描述

文心一言:(了解)
该程序用于查看和分析ELF文件的信息。它显示了ELF文件类型为DYN(共享对象文件),并且有一个名为“hello”的入口点地址为0x1060。有13个程序头信息,从偏移量64开始。这些程序头信息包括类型、偏移量、虚拟地址、物理地址、段描述符表大小、文件大小、可执行文件大小、交换文件大小、运行时大小、装载大小、动态大小、GNU_PROPERTY、GNU_STACK和GNU_EH_FRAME等字段。

  1. 段表(Section Header Table):描述文件中各个段(section)的详细信息。
$ readelf -S hello

列出每个节区的名称、类型、大小、地址等信息

  1. 段(section):实现存储代码、数据、符号表、重定位信息和调试信息等内容。ELF的核心部分,包含程序运行需要的所有文件
ELF文件中的段

一个典型的ELF文件包含下面几个段:

  • .text:已编译程序的指令代码段。
  • .rodata:ro表示read only,即只读数据。
  • .data:已初始化的C程序全局变量和静态局部变量。
  • .bss:未初始化的C程序全局变量和静态局部变量。
  • .debug:调试符号表,调试器用此段的信息帮助调试。
    在这里插入图片描述
反汇编ELF

因为ELF无法当作普通文件打开,若希望直接查看一个ELF文件包含的指令和数据,则需要用反汇编方法。

使用objdump -D,对hello进行反汇编,如下所示:

$ objdump -D hello

作者不会汇编且汇编文件过长所以 建议读者自己尝试

使用objdump -S将其反汇编并且将其C语言源代码混合显示出来:

$ gcc -o hello -g hello.c
$ objdump -S hello

参考文献

GCC学习总结 --作者:studyingdda
gcc编译器背后的故事 --作者:Loopy睿
GCC 指令详解及动态库、静态库的使用方法 --作者:wyhua2008

总结

在写笔记过程中,参考大家的内容也学习到了很多知识,正所谓温故知新。有不妥之处请各位指点。

### 如何在Linux中使用GCC编译器 #### 安装GCC编译器 为了确保能够在Linux系统中顺利使用GCC编译器,首先需要确认该编译器已经安装。大多数Linux发行版默认已包含GCC;如果没有,则可以通过包管理器轻松安装[^2]。 对于基于Debian/Ubuntu系统的用户来说,只需运行如下命令即可完成安装: ```bash sudo apt update && sudo apt install build-essential ``` 而对于Red Hat/CentOS/Fedora等RPM包管理系统而言,应执行以下指令来获取最新版本的GCC套件: ```bash sudo yum groupinstall "Development Tools" ``` #### 基础用法说明 一旦成功安装好GCC之后,在终端窗口输入`gcc --version`可以验证当前所使用的具体版本号以及路径位置。接着便能着手编写简单的C语言源码文件(.c),并通过指定目标输出名称的方式将其转换为二进制形式: 假设有一个名为hello.c的小例子,那么完整的构建过程应当像这样操作: ```bash gcc hello.c -o hello_program ./hello_program ``` 上述两行分别代表了两个阶段的任务——首先是调用GCC解析并组装机器指令集至最终产物(这里指代的是可直接被执行的应用程序),其次是实际启动这个新生成的对象文件来进行测试或者正式投入使用[^1]。 #### 高级特性概览 除了基本的功能之外,GCC还提供了丰富的配置参数允许开发者自定义更多细节上的设定,比如优化级别(-O)、调试信息嵌入(-g)或是静态库链接(-static)[^3]。这些附加选项有助于提高性能表现的同时也为后续可能出现的问题排查工作打下了坚实的基础。 例如开启最高级别的优化措施同时保留必要的追踪线索供日后分析之用: ```bash gcc -O3 -ggdb myapp.c -o optimized_myapp ``` #### 版本共存机制 值得注意的一点在于同一个操作系统实例上面是可以合法存在不同年代发布的多个GCC变种版本,并且互不影响正常使用情况下的各自独立运作状态。这意味着当项目依赖特定历史时期的ABI(Application Binary Interface)/API(Application Programming Interface)标准时,可以选择性地切换到相匹配的那个分支继续开展业务逻辑编码活动而不必担心兼容性的困扰[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值