Linux 编译器 gcc

2023年 10月 2日 83.5k 0

一. 初探 gcc

GCC 英文全名为 GNU Compiler Collection, 早期的 gcc 编译器主要用于 C 语言编译, 但是经过几十年的发展, 其可以用于多种语言的编译, 例如 C++, Go 等目前较为主流的语言.

对于 gcc 而言, 它是一款 Linux 中的 编译器, 可以和之前所提及的编辑器 vim 配合使用.

gcc 的意义

对于平常的应用程序开发, 我们很少需要关注编译和链接过程, 因为通常的开发环境都是流行的 集成开发环境 (IDE), 比如常见的 Visual Studio. 这样的 IDE 一般都将编译和链接的过程一步完成, 通常将这种编译和链接合并到一起的过程称为 构建 (Build). 即使使用命令行来编译一个源代码文件, 简单的一句 "gcc test.c" 命令就包含了非常复杂的过程.

所以为了了解很多系统软件的运行机制和机理, 学习 gcc.

二. gcc 编译源程序分步拆解

GCC 编译源程序可以被拆解为 4 个步骤, 分别是预处理 (Prepressing), 编译 (Compilation), 汇编 (Assembly) 和链接 (Linking).

image.png

cpp - C Preprocessor C预处理器

as - assembler 汇编器

ld - link editor 链接器

1. 预编译 (预处理)

预编译 主要处理源代码中的以“#”开始的预编译指令,如:#include, #define 等,其主要处理规则如下:

  • 宏替换: 将所有的 #define 删除, 并且 展开 所有的 宏定义.
  • 条件编译: 处理所有 条件预编译 指令, 如: #if, #ifdef, #elif, #else, #endif.
  • 头文件展开: 处理 "#include" 预编译指令, 将被包含的文件插入到该预编译指令的位置. 该过程是递归进行的, 因为被包含的文件可能还包含其他文件.
  • 去注释: 删除所有的注释 "//" 和 "/* */".
  • 添加行号和文件名标识, 比如 #2 "hello.c" 2, 以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或警告时能够显示行号.
  • 保留所有的 #pragma 编译器指令, 如: #pragma once 用于保证头文件只被编译一次, #pragama pack 用于指定内存对齐(一般用在结构体), 因为编译器需要使用它们.

预编译生成的 .i 文件不包含任何宏定义, 因为所有的宏已经被展开. 并且包含的文件也已经被插入到 .i 文件中. 所以当我们无法判断宏定义是否正确或头文件包含是否正确时, 可以查看预编译后的文件来确定问题.

预编译步骤相当于执行如下命令:

$ gcc -E hello.c -o hello.i
  • -E 表示只进行预处理而不进行编译.
  • -o 后跟生成的文件名称.
  • .c 结尾的文件表示源程序.
  • .i 结尾的文件表示已经过预处理的 C 原始程序.

image.png

2. 编译

编译就是把预处理生成的文件进行一系列词法分析, 语法分析, 语义分析, 优化后生成相应的汇编代码文件, 这个过程是整个程序构建的核心部分, 也是最复杂的部分之一.

编译步骤相当于执行如下命令:

$ gcc -S hello.i -o hello.s
  • -S 表示只进行编译而不进行汇编, 生成汇编代码.
  • -o 后跟生成的文件名称.
  • .i 结尾的文件表示已经过预处理的 C 原始程序.
  • .s 结尾的文件表示经过编译生成的汇编代码.

image.png

3. 汇编

汇编就是将汇编代码转换成机器可以执行的指令, 每一个汇编语句几乎都对应一条机器指令. 所以汇编过程相对于编译比较简单, 其没有复杂的语法, 语义, 也无需做指令优化, 只需根据汇编指令和机器指令的对照表一一进行翻译就可以了.

汇编步骤相当于执行如下命令:

$ gcc -c hello.s -o hello.o
  • -c 表示只进行汇编而不进行链接, 生成目标文件.
  • -o 后跟生成的文件名称.
  • .s 结尾的文件表示经过编译生成的汇编代码.
  • .o 结尾的文件表示经过汇编生成的可重定位目标二进制文件.

image.png

4. 链接

链接就是将可重定位目标二进制文件和库文件进行链接, 形成可执行程序.

链接步骤相当于执行如下命令:

$ gcc hello.o -o hello
  • -o 后跟生成的文件名称.
  • .o 结尾的文件表示经过汇编生成的可重定位目标二进制文件.
  • 没有后缀的 hello 文件表示链接后形成的可执行程序.

可执行文件的内容为可被机器识别的二进制码.

image.png

三. 动静态库及动静态链接

头文件 & 库文件

头文件

预处理过程包含头文件展开, 头文件究竟在哪里呢?

$ ls /usr/include/

即可查看系统下安装了哪些头文件.

image.png

用 vim 打开其中的头文件 stdio.h, 发现其包含若干库函数的声明 ( 比如 printf).

image.png

ldd 指令

ldd [文件名] - 检测可执行程序形成的时候都依赖了哪些动态库
  • l : list (列出)
  • d : dynamic (动态的)
  • d : dependencies (依赖关系)

ldd hello, 显示出可执行程序 hello 所依赖的动态库.

image.png

  • => 左边的是程序需要连接的共享库
  • => 右边的是由 Linux 搜索到的共享库在文件系统中对应的具体位置

提示: 指令本质就是可执行文件, 也可以通过 ldd 指令查看其所依赖的动态库.

image.png

库文件

链接过程所链接的库文件, 究竟在哪里呢?

ll /usr/lib64

即可找到系统下安装的库文件.

image.png

用 vim 打开 C标准库 libc.so.6.

image.png

该库文件为 ELF 文件, 是一种用于二进制文件, 可执行文件, 目标代码, 共享库和核心转储格式文件的文件格式.

如果说头文件提供函数的声明, 那么库文件则提供函数的实现. 函数的实现就在库文件中, 库文件其实就是把源文件经过一定的翻译, 然后打包为一个文件提供给用户, 这样就无需提供过多的源文件, 也可以达到隐藏源文件的目的.

动态库 & 静态库

上文提及的 libc.so.6 实际上就是 C动态库. 动静态库有自己的命名规则, 当我们去掉一个动静态库的前缀lib, 再去掉后缀 .so 或者 .a 及其后面的版本号, 剩下的就是这个库的名字.

动态库

Linux 下, 以 .so 为后缀的是动态库.

image.png

Windows 下, 以.dll为后缀的是动态库.

image.png

静态库

Linux 下, 以 .a 为后缀的是静态库.

image.png

Windows 下, 以.lib为后缀的是静态库.

image.png

动态链接 & 静态链接

动态链接 & 静态链接 实际操作

gcc 链接默认为动态链接.

image.png

若想静态链接, 得加上 -static 选项, 改变优先级.

image.png

然而出现了报错, 这是因为 linux 下默认只安装动态库, 没有安装静态库.

C静态库安装指令如下:

$ (sudo) yum install -y glibc-static

安装好 C静态库之后再进行静态链接.

image.png

发现静态链接形成的可执行文件比动态链接形成的可执行文件大 100 倍.

image.png

动态链接 & 静态链接 优缺点

  • 静态链接

静态链接是程序在链接的时候把静态库的代码复制到可执行文件当中的, 生成的可执行程序在运行的时候将不再需要静态库, 因此使用静态库生成的可执行程序一般比较大.

优点: 使用静态库生成可执行程序后, 该可执行程序就可以独自运行, 不再需要库了.

缺点:使用静态库生成可执行程序会占用大量空间, 特别是当有多个静态程序同时加载而这些静态程序使用的都是相同的库, 这时在内存当中就会存在大量的重复代码.

  • 动态链接

动态链接是程序在运行的时候才去链接相应的动态库代码的, 多个程序共享使用库的代码. 一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表, 而不是外部函数所在目标文件的整个机器码.

在可执行文件开始运行前, 外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中, 这个过程称为动态链接. 动态库在多个程序间共享, 节省了磁盘空间, 操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用, 节省了内存和磁盘空间.

image.png

优点: 节省磁盘空间, 且当多个用到相同动态库的程序同时运行时,库文件会通过进程地址空间进行共享, 内存当中不会存在重复代码.

缺点: 必须依赖动态库,否则程序无法运行.

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论