1.深入浅出gcc/g++编译链接过程

gcc/g++

GCC程序语言编译器自由软件,现在可以编译很多语言,gcc 是GCC中的c编译器,而g++ 是GCC中的c++编译器。
指令格式gcc 选项 文件名

公共选项

-o 文件名:指定输出文件名

预处理

作用

  1. 处理所有#开头的预处理指令
  • 头文件包含(#include):将目标头文件的内容复制到当前文件中;
  • 宏定义(#define)及宏替换;
  • 条件编译(#ifdef#ifndef#if 0 等);
  1. 删除注释
  2. 预编成一个.i文件

选项

  1. -E 只进行预处理
  2. -I路径:添加头文件搜索路径,可多次使用
  3. -D宏名\[=值],常用于控制条件编译
    gcc -DDEBUG -DVERSION=1.0 -E main.c -o main.i -I./include

编译

此步最耗时,因为要检查语法语义错误,对于这些控制选项也是最多的

作用

  1. 进行语法、语义检查
  2. 生成平台相关的汇编代码.s文件

选项

  1. -S 只进行到编译
  2. -O0/O1/O2/O3 优化级别递增,默认O0不优化
  3. -Wall 启用大多数警告,默认显示优先级最高的警告
  4. -w 禁止所有警告
  5. -g 生成调试信息,用于gdb调试
  6. -std=标准 指定语言标准,如c11

汇编

作用

  1. 将汇编代码转换为机器指令获得目标文件.o

选项

  1. -c 只进行到汇编

链接(静态)

作用

本质就是对主文件中的调用函数填上对应地址,查找函数地址顺序:主文件-->显示指定二进制文件-->显示指定库文件-->标准库中文件

  1. 多个二进制的目标文件链接成一个单独的可执行文件
  2. 找到依赖的库文件(静态与动态)

选项

-L 解决"库文件在哪里"的问题(系统标准库不需要指定),-l 解决"需要哪些库符号"的问题(涉及到库的都需要指定),一般第三方库需要两者配合使用

  1. -L目录 添加库文件搜索路径
  2. -l库名 链接指定库,如-lpthread,注意这里是小写的l

预处理器

对于#include,就是粘贴复制头文件中代码

obj单定义规则: 全局变量和函数只能有一个定义,类、模板以及内联函数可以在多个翻译单元中有完全相同的定义,所以不要将函数和变量定义放在头文件中。
代码高知h和.cpp应该成对出现
使用双引号时,预处理器知道这是编写的头文件。预处理器首先在当前目录中搜索头文件。如果找不到匹配的头文件,将搜索系统目录。
没有.h扩展名头文件,声明了std命名空间中的所有标识符,使用标准库头文件时,优先使用不带.h扩展名的版本,即std命名空间中的标识符,这也是为什么引入string之后,却使用std::string的原因
当引入其他目录下的头文件时,尽量别使用相对路径,而是更改单个编辑器的设置,g++ -o main -I/source/includes main.cpp,-I后面没有空格

对于ifdef、ifndef、endif,只对单文件使用

#if 0 可用于注释

对于#define,进行简单的文本替换

链接器

链接器将程序中的变量、函数等符号(Symbols) 转换为可执行文件中的内存地址

  1. 符号地址的分配方式
  • 静态链接(非PIC) : 链接器为符号分配绝对虚拟地址 (如 0x400520),这些地址在程序加载时固定不变。 示例main函数可能位于 0x401000,其他函数按编译顺序依次排列。
  • 动态链接(PIC) : 使用位置无关代码 时,符号地址为相对于加载基址的偏移 ,通过全局偏移表GOT动态计算实际地址。
  1. 动态库函数的处理(PLT/GOT) 动态库函数(如 printf)的调用通过以下机制实现:
  • PLT(过程链接表) : 首次调用 printf@plt 时,PLT跳转到动态链接器(ld-linux.so)解析函数真实地址,并更新到GOT中。

  • GOT(全局偏移表) : 存储动态库函数的实际内存地址,后续调用直接跳转到GOT中的地址,避免重复解析。

    C 复制代码
    // 首次调用触发地址解析
    printf("Hello");  // 编译为 call printf@plt
  1. 未解析符号的报错规则
  • 静态链接阶段 : 若符号未在目标文件(.o)或静态库(.a)中定义,直接报错 undefined reference
  • 动态链接阶段 : 若动态库缺失或符号未找到(如运行时 libfoo.so 未安装),程序加载时报错 cannot open shared object file

初始化列表

定义

定义变量时同时提供初始值,提供初始值的方式有:

cpp 复制代码
int a;//默认初始化,未赋值不要直接打印
int b = 2;//拷贝初始化
int c(3);//直接初始化
//列表初始化(统一初始化)
int d{};
int e = {3};
int f{ 2 };

推荐使用列表初始化,这样对于数组也是统一的,而且不允许大类型转小类型,比如使用int a = {2.5};是会报错的,而使用int a = 2.5;就会默认丢失,此时a为2。

库本质是 目标文件(.o)的集合,即一堆函数的集合,但是去掉了编译(预处理编译汇编)过程(最耗时的部分)

静态库

有两个文件:头文件和库文件.a/.lib,库文件(二进制文件)提供函数的具体实现,链接时会查找库文件,将未描述的符号写入目标文件

制作lib库名.a

  1. 将.c生成.o文件gcc -c add.c -o add.o
  2. 使用ar工具制作静态库ar rcs lib库名.a add.o sub.o div.o

使用

  1. 源代码中引入库的头文件
  2. 编译静态库到可执行文件中gcc test.c lib库名.a -o a.out

链接器按 从左到右的顺序 处理输入文件,且只在处理到该库时 检查当前未解析符号,并提取需要的 .o 文件,所以如果将库名和源文件顺序交换,则会报错,流程:

  1. 先处理库文件,此时没有任何未解析符号,因为没有编译test.c
  2. 连接器认为不需要该库中任何内容,跳过整个库
  3. 再编译test.c生成test.o,并发现存在未解析的函数(即找不到定义)
  4. 查找标准库,并将未解析的函数填上地址,如printf()
  5. 发现最终依然存在未解析的函数,报错

动态库

动态库(.so.dll)在编译时不会被完全链接到可执行文件中,而是在程序运行时发现少了该库,如果该库没有转载到内存,则会转载到内容并获得所需符号的地址。动态库的代码(函数入口地址)可以被多个程序共享,减少内存占用,且更新库文件无需重新编译主程序。

制作 lib库名.so(Linux)或 库名.dll(Windows)

  1. 生成位置无关的目标文件

    Bash 复制代码
    gcc -c -fPIC add.c sub.c div.c  # -fPIC 生成位置无关代码(Position Independent Code)
  • 输出:add.o, sub.o, div.o
  1. 将目标文件打包为动态库

    Bash 复制代码
    # Linux
    gcc -shared -o lib库名.so add.o sub.o div.o
    
    # Windows(MinGW)
    gcc -shared -o 库名.dll add.o sub.o div.o
  • 输出:lib库名.so(Linux)或 库名.dll(Windows)

使用动态库

  1. 源代码中引入头文件

  2. 编译时链接动态库

    Bash 复制代码
    gcc test.c -o a.out -L. -l库名 -I头文件目录
  3. 运行时加载动态库

    • Linux :设置 LD_LIBRARY_PATH 环境变量

      Bash 复制代码
      export LD_LIBRARY_PATH=动态库路径//只会对当前bash生效,且如果关闭后会丢失
    • Windows :将 .dll 文件放在以下任一目录:

      • 可执行文件所在目录
      • 系统目录(如 C:\Windows\System32
      • PATH 环境变量包含的目录

编译后生成的可执行文件,如果系统中找不到动态库,会报错

相关推荐
君鼎1 小时前
C++设计模式——单例模式
c++·单例模式·设计模式
不知几秋1 小时前
数字取证-内存取证(volatility)
java·linux·前端
刚入门的大一新生3 小时前
C++初阶-string类的模拟实现与改进
开发语言·c++
小冯的编程学习之路3 小时前
【软件测试】:推荐一些接口与自动化测试学习练习网站(API测试与自动化学习全攻略)
c++·selenium·测试工具·jmeter·自动化·测试用例·postman
欧先生^_^4 小时前
Linux内核可配置的参数
linux·服务器·数据库
C++ 老炮儿的技术栈4 小时前
什么是函数重载?为什么 C 不支持函数重载,而 C++能支持函数重载?
c语言·开发语言·c++·qt·算法
海尔辛5 小时前
学习黑客5 分钟读懂Linux Permissions 101
linux·学习·安全
猪八戒1.05 小时前
C++ 回调函数和Lambda表达式
c++
源远流长jerry5 小时前
匿名函数lambda、STL与正则表达式
c++
王RuaRua6 小时前
[数据结构]5. 栈-Stack
linux·数据结构·数据库·链表