15分钟搞懂——GCC、动态库、静态库相关概念

本文章用于个人学习。全文AI生成,个人整理,现在分享出来。

GCC简介与其主要功能

GCC(GNU Compiler Collection)是一个由GNU项目开发的编译器集合。它最初是作为C语言编译器(GNU C Compiler)而创建的,后来逐渐扩展,能够支持多种编程语言的编译,包括但不限于C、C++、Objective - C、Fortran、Ada等。

  • 编译功能:将高级语言源代码转换为机器可执行代码。例如,对于C语言程序,它会将.c文件中的代码经过一系列复杂的处理过程,最终生成可执行的二进制文件(如在Linux系统下生成的可执行文件)。这个过程包括词法分析、语法分析、语义分析等多个阶段。在词法分析阶段,它会将源代码中的字符序列分解成一个个的记号(token),比如关键字、标识符、运算符等。在语法分析阶段,根据语言的语法规则,将这些记号组织成语法树,以确定代码的结构。
  • 优化功能:GCC可以对生成的代码进行优化,以提高程序的运行效率。优化级别可以通过编译选项来控制。例如,使用-O1选项可以进行基本的优化,包括一些简单的指令调度等;而-O2选项会进行更深入的优化,像函数内联(将函数调用替换为函数体的内容,减少函数调用开销)、循环展开(将循环体展开,减少循环控制的开销)等。这些优化措施可以让程序在执行时更快地运行,更有效地利用系统资源。

gcc(ESC-iso)

生成可执行文件的过程通常包括几个关键步骤:预处理、编译、汇编和链接。下面是对这些步骤的详细解释:

1. 预处理(Preprocessing)(-E .i)

在预处理阶段,C语言源代码中的预处理指令(如#include, #define等)会被处理。
这一步骤会将头文件的内容插入到源文件中,并展开宏定义。
例如,#include <stdio.h> 会让编译器将标准输入输出库的声明插入到你的源代码中。

命令示例:

gcc -E star.c -o star.i

这个命令会生成一个.i文件,其中包含了预处理后的代码。

2. 编译(Compilation)(-S .s)

编译阶段是将经过预处理的代码翻译成特定于目标机器的汇编语言代码。在这个过程中,语法检查也会被执行,如果源代码中有语法错误,编译器会在此时报告。

命令示例:

gcc -S star.i -o star.s

这个命令会将预处理后的文件转换为汇编语言文件(.s)。

3. 汇编(Assembly)(-c .o)

汇编步骤将汇编语言代码转换为目标机器的二进制机器码,结果是一或多个目标文件(.o.obj),这些文件包含的是机器码但还不是一个完整的可执行程序。

命令示例:

gcc -c star.s -o star.o

这个命令会将汇编语言文件转换为目标文件。

4. 链接(Linking)( )

链接阶段是最复杂的部分之一,它涉及将一个或多个目标文件与合适的库文件组合起来以创建最终的可执行文件。
在这个过程中,所有外部函数调用都被解析并指向实际的函数实现。如果你的应用程序依赖于其他库(比如标准C库),这些库会在链接阶段被合并进来。

命令示例:

gcc star.o -o star

这条命令将目标文件链接成名为star的可执行文件。

总结、一条GCC命令来完成

![[Pasted image 20250517015555.png]]
整个过程可以用一条GCC命令来完成,如下所示:

gcc star.c -o star

此命令自动完成了上述的所有四个步骤:预处理、编译、汇编和链接,并直接生成了可执行文件star。这种方式非常方便,因为它简化了构建过程,尤其对于小型项目来说非常适合。对于更复杂的项目,可能需要使用Makefile或其他构建工具来管理不同的构建步骤和依赖关系。

静态库与动态库

更直白地解释动态库和静态库的区别:

静态库(Static Library)

  • 打包方式:静态库就像是把所有需要的工具都放进一个大箱子里,然后把这个箱子直接寄给你。
  • 编译时:当你使用静态库来编译你的程序时,编译器会把整个“箱子”里的东西(即库代码)都复制到你的程序中。最后生成的可执行文件是独立的,不需要额外的库文件来运行。
  • 优点
    • 简单部署:因为所有依赖都被包含在最终的可执行文件里了,所以你只需要分发这个单一文件即可。
  • 缺点
    • 文件较大:由于每个使用该库的程序都会包含一份库代码的副本,因此生成的可执行文件可能会比较大。
    • 更新不便:如果库更新了,所有使用该库的程序都需要重新编译。

归档文件(Archive File)是一种包含多个文件和/或目录的单个文件。它通常用于将多个数据文件打包在一起。


静态库的使用
1、创建静态库(生成归档文件)
  • ==ar:这是Unix/Linux系统下用于创建和操作归档文件的工具。==特别地,它被广泛用于创建静态库。
    • 使用ar创建一个名为libexample.a的归档文件,并向其中添加file1.ofile2.o目标文件的命令如下:

      ar -rcs libexample.a file1.o file2.o
      
    • -r表示插入指定文件进入归档;如果该文件已存在,则替换旧文件。

    • -c选项告诉ar创建归档文件(即使对于大多数现代版本的ar来说这不是必需的,因为当使用-r时,若归档不存在则自动创建)。

    • -s选项用于更新归档中的符号索引表,这在创建静态库时很有用。


2、进一步编译生成可执行文件
gcc star.c libhello.a -o mystar
  • gcc:调用GNU编译器集合来编译和链接源代码。

  • star.c:这是你的主程序源文件,包含了 main() 函数以及其他可能需要调用 libhello.a 中函数的代码。

  • libhello.a:这是你在第一步中创建的静态库。在这里,GCC将会把静态库中的所有相关目标文件链接到最终生成的可执行文件中。

  • -o mystar:指定输出的可执行文件名为 mystar。如果不指定 -o 参数,默认输出文件名通常是 a.out


动态库(Shared Library)

  • 打包方式:动态库更像是把工具放在一个公共的地方,每个人都可以去借用,但不会把它们带走。
  • 运行时:当你的程序运行时,它会根据需要从系统中加载这些共享库。这意味着多个程序可以共享同一个库文件,而不是各自携带一份副本。
  • 优点
    • 节省空间:因为多个程序可以共享同一个库文件,减少了总的磁盘占用。
    • 易于维护:更新库文件后,所有依赖它的程序无需重新编译就能受益于更新。
  • 缺点
    • 部署复杂:你需要确保目标机器上有正确的库版本,否则程序可能无法运行。
    • 启动稍慢:程序启动时需要花时间查找并加载所需的库文件。

简单来说:

  • 如果你想要简化部署流程且不担心文件大小,可以选择静态库。
  • 如果你希望节省磁盘空间,并且想让库的更新更加方便,则应选择动态库。

动态库的使用

以下是对你提供的命令的逐行解释,结合知识库中的信息进行详细说明:

流程总结
命令作用
gcc -c -fpic hello.c生成位置无关代码的目标文件 hello.o
gcc -shared -s -o libhello.so hello.o将目标文件打包成共享库 libhello.so
cp libhello.so /usr/lib将共享库安装到系统默认路径,方便其他程序调用
gcc -lhello star.c -o mystar编译 star.c 并链接 libhello.so,生成可执行文件 mystar
ldd mystar检查 mystar 依赖的动态库是否正确加载
./mystar运行程序,验证动态库功能是否正常

关键知识点回顾
  1. 动态库(.so)的生成

    • 必须使用 -fpic 生成位置无关代码。
    • 使用 -shared 选项将目标文件打包为共享库。
  2. 动态库的使用

    • 需要将 .so 文件放在系统默认路径(如 /usr/lib)或通过环境变量 LD_LIBRARY_PATH 指定路径。
    • 链接时使用 -lxxx(如 -lhello)自动查找 libxxx.so
  3. 常见错误及解决

    • 错误:relocation R_X86_64_PC32:未加 -fpic 编译目标文件。
    • 错误:cannot open shared object file:动态库未安装到系统路径或未设置 LD_LIBRARY_PATH

1、生成位置无关代码 (.o)
gcc -c -fpic hello.c
  • 作用:将 hello.c 编译为目标文件 hello.o,并生成 位置无关代码(Position Independent Code, PIC)。
  • 关键参数
    • -c:仅编译不链接,生成目标文件(.o 文件)。
    • -fpic-fPIC:这是指生成位置无关代码(Position Independent Code)。
  • 这种代码可以在内存中的任何地址加载和运行,而不需要根据其绝对地址进行调整。
  • 这对于创建共享库特别重要,因为共享库可能被加载到不同的内存地址中。
  • 如果不加 -fpic,生成的 .o 文件无法用于共享库,会导致链接时出错(如 relocation R_X86_64_PC32 错误)。

  • PIC(Position Independent Code):这种类型的代码允许在内存中任意位置加载和执行,这使得多个进程可以共享同一个库的副本,节省内存空间。

  • 为什么需要 PIC? 在创建共享库时,必须使用 -fpic-fPIC 选项来确保库可以在不同进程中正确地工作。如果未使用该选项,则可能会遇到重定位错误或其他问题。

  • 结果:生成 hello.o 文件(目标文件)。


2、生成共享库(.so)
gcc -shared -s -o libhello.so hello.o
  • 作用:将 hello.o 编译成共享库 libhello.so

  • 关键参数

    • -shared:告诉 GCC 生成共享库(.so 文件)。
    • -s:移除符号表(strip),减小文件体积(调试信息会被删除)。
    • -o libhello.so:指定输出文件名为 libhello.so
  • 知识库补充

    • 共享库的命名规则通常是 libxxx.so(如 libhello.so),这是 Linux 系统默认的动态库命名方式。
    • 如果不加 -shared,GCC 会生成普通可执行文件,而不是共享库。
  • 结果:生成 libhello.so 文件(共享库)。


(1 和 2 可用下面命令替代)
gcc -fpic -shared -s hello.c -o libhello.so
3、将共享库拷贝到 系统默认的 库搜索路径
cp libhello.so /usr/lib
  • 作用:将共享库 libhello.so 拷贝到系统默认的库搜索路径 /usr/lib
  • 知识库补充
    • Linux 系统在运行程序时,默认会在 /usr/lib/lib 等路径中查找动态库。将库文件放在此处后,其他程序无需手动指定路径即可找到它。
    • 注意:需要 root 权限才能写入 /usr/lib,因此使用 root 用户执行此命令。

4、链接动态库,生成可执行文件
gcc -lhello star.c -o mystar
  • 作用:编译 star.c 并链接 libhello.so,生成可执行文件 mystar
  • 关键参数
    • -lhello:表示链接名为 libhello.so 的库(GCC 会自动补全为 libhello.so)。
    • star.c:需要调用 libhello.so 中函数的源文件。
    • -o mystar:输出可执行文件名为 mystar

知识库补充

  • 链接时,GCC 会在系统默认的库路径(如 /usr/lib)中查找 libhello.so,因此需要提前将库文件拷贝到该路径。
  • 如果库文件不在默认路径中,需要手动指定路径(如 -L/path/to/library)。
  • 结果:生成可执行文件 mystar,它依赖 libhello.so

5、查看 可执行程序 的依赖的动态库及其路径
ldd mystar
  • 作用:查看 mystar 依赖的动态库及其路径。
  • 输出示例
    libhello.so => /usr/lib/libhello.so (0x4002d000)
    libc.so.6 => /lib/tls/libc.so.6 (0x42000000)
    /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
    
  • 关键信息
    • libhello.so 被正确找到并加载到地址 0x4002d000
    • libc.so.6 是 C 标准库,所有 C 程序都依赖它。
    • ld-linux.so.2 是 Linux 的动态链接器(负责加载动态库)。

(1)

gcc -c -fpic hello.c
  • -fpic-fPIC:这是指生成位置无关代码(Position Independent Code)。

  • 这种代码可以在内存中的任何地址加载和运行,而不需要根据其绝对地址进行调整。

  • 这对于创建共享库特别重要,因为共享库可能被加载到不同的内存地址中。

    • PIC(Position Independent Code):这种类型的代码允许在内存中任意位置加载和执行,这使得多个进程可以共享同一个库的副本,节省内存空间。
    • 为什么需要 PIC? 在创建共享库时,必须使用 -fpic-fPIC 选项来确保库可以在不同进程中正确地工作。如果未使用该选项,则可能会遇到重定位错误或其他问题。

(2)

gcc -shared -s -o libhello.so hello.o
  • -shared:此选项指示 GCC 生成一个共享库而不是一个可执行文件。共享库可以在运行时被加载到内存,并且可以同时被多个程序使用,有助于节省内存和磁盘空间。

  • -s:这个选项会在生成的输出文件中去除所有的符号表和重定位信息。这意味着最终生成的共享库会更小,但调试起来也会更加困难,因为没有这些信息可以帮助定位问题。

  • -o libhello.so:指定输出文件的名字。在这个例子中,输出将是一个名为 libhello.so 的共享库文件。共享库的名字通常遵循 lib[name].so 的命名约定,以便于后续的链接操作能够找到它们。

  • hello.o:这是输入的目标文件之一,GCC 将把这个目标文件和其他可能的输入文件一起链接成一个共享库。如果项目中有多个源文件,你可能会看到更多类似的目标文件作为输入。


(1)(2)也可以用下边命令替代

gcc -fpic -shared -s hello.c -o libhello.so
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值