核心编译器

这是将源代码(.cpp)转换成目标文件(.o/.obj)的核心工具。

GCC作为最主流和最广泛的编译器,典型应用场景包括Linux开发、跨平台项目、嵌入式系统。其远不止一个C++编译器,它是一个强大的编译器集合,它能够编译多种编程语言,并支持众多硬件平台和操作系统。常见的语言C,C++,GO,D,Rust等都可以通过GCC进行编译,其中对C和C++的支持是最成熟和最广泛使用的

除此之外,GCC能面向的平台很广泛,常用的平台架构x86/x86_64,ARM它都可以支持。这也就让GCC有了可以交叉编译的特性(即在一台机器上(主机,如x86笔记本电脑)编译生成能在另一种架构的机器上(目标,如ARM开发板)运行的程序)。要实现这个功能,需要安装对应的交叉编译工具链。例如,在Ubuntu上为树莓派(ARM)编译程序,可以安装 gcc-arm-linux-gnueabihf 包,然后使用 arm-linux-gnueabihf-gcc 命令来编译C代码。

C++各文件说明

可执行文件

Linux系统下的GCC编译

编译C语言:gcc hello.c -o hello

  • 输入文件hello.c(C 语言源代码)
  • 输出文件hello(可执行文件,无扩展名)
  • 运行方式./hello

编译C++语言:g++ hello.cpp -o hello

  • 输入文件hello.cpp(C++ 源代码)
  • 输出文件hello(可执行文件,无扩展名)
  • 运行方式./hello

Windows系统下的GCC编译

Windows 上的 GCC 通常通过 ​MinGW​ 或 ​MSYS2​ 提供,命令与 Linux 类似,但生成的文件格式不同。

编译C语言:gcc hello.c -o hello.exe

  • 输入文件hello.c(C 语言源代码)
  • 输出文件hello.exe(Windows 可执行文件,带 .exe 扩展名)
  • 运行方式hello.exe(在 CMD 中直接输入)

编译C++语言:g++ hello.cpp -o hello.exe

  • 输入文件hello.cpp(C++ 源代码)
  • 输出文件hello.exe
  • 运行方式hello.exe

目标文件(.o)(不链接)

gcc -c hello.c   # 生成 hello.o
g++ -c hello.cpp # 生成 hello.o

目标文件(.o 或 .obj)是编译器生成的中间文件,包含源代码编译后的机器码,但尚未链接成最终的可执行文件或库。也就是说它仅包含当前源文件(如 main.cpp)编译后的机器码,包括该文件定义的函数和全局变量,尚未链接外部库或依赖。

那目标文件如何处理 #include 和外部依赖? 这里可以进行说明。首先,编译阶段(生成.o文件),对于#include头文件(如 <iostream>)通常只包含声明,编译器生成 .o 文件时,会标记所有未解析的符号​(如 std::cout),留待链接阶段解决。接下来进行链接阶段(生成可执行文件),此时链接器会​合并所有 .o 文件并查找静态库(.a)或动态库(.so/.dll,将标记的未解析符号(如 std::cout)与库中的实现绑定,最终生成可执行文件,其中所有符号地址均已确定。。​

.o文件作为中间文件有什么作用呢?首先,它可以进行分离编译,加快大型项目构建,例如,当项目有多个源文件(如 main.cpputils.cppmath.cpp)时,修改其中一个文件只需重新编译该文件,无需重新编译整个项目。具体实现可以参照如下,使用命令"g++ -c utils.cpp"将修改的utils.cpp文件只进行编译生成utils.o文件,然后通过命令"g++ main.o utils.o -o program"重新链接即可(main.cpp调用了utils.cpp)。除此之外,.o文件还是静态库(.a/.lib)的构建基础,静态库本质上就是一组目标文件的打包(通过 ar 工具),例如,通过命令“g++ -c utils.cpp math.cpp”生成文件utils.o和math.o,然后通过命令"ar rcs libmylib.a utils.o math.o"打包成静态库

需要注意,目标文件(.o文件)不可以跨平台使用,与之前交叉编译生成的

可执行文件不同。

汇编代码(.s)

gcc -S hello.c   # 生成 hello.s

头文件(.h)

头文件(.h 文件)是 C++ 项目的接口声明文件,它定义了模块或库的“使用说明书”,但不包含具体实现。它的核心作用可以概括为以下三点:

  1. 声明接口:告诉编译器有哪些函数、类、变量可用以及​它们的调用方式​(函数签名、类定义、变量类型等)
  2. 支持代码复用:头文件允许你在多个 .cpp 文件中共享相同的声明,避免重复编写。
  3. ​防止重复包含:头文件经常被多个 .cpp 文件包含,为避免重复定义,需使用 ​**#pragma once#ifndef 宏**。

静态库(.a/.lib)

静态库(Static Library)是一组预编译的目标文件(.o 或 .obj)的集合,通过归档工具(如 ar)打包成单个文件(Linux 下为 .a,Windows 下为 .lib)。它在编译时被完整复制到最终的可执行文件中,生成一个独立的、无需外部依赖的二进制程序。

静态库生成过程:

  1. 编译源代码为目标文件(.o)​:g++ -c utils.cpp math.cpp

  2. 打包目标文件为静态库:ar rcs libmylib.a utils.o math.o   # Linux/Mac

静态库的核心用途可概括为以下几项:

  1. 代码复用:将常用功能(如数学计算、日志模块)封装为库,供多个项目共享。避免重复编译相同代码,提升开发效率。
  2. 模块化开发:大型项目中,团队分工开发不同模块(如网络模块、UI模块),各自生成静态库后统一链接。
  3. 保护源代码:分发库文件(.a/.lib)而非源代码(.cpp),隐藏实现细节。静态库仍可通过反汇编逆向,但比直接提供源码更安全。
  4. 性能优化:对性能敏感的代码(如高频调用的数学函数),编译为静态库时可启用激进优化(如 -O3)。

链接静态库:g++ main.cpp -L. -lmylib -o program  # Linux/Mac

  • ​**-L.**​:指定库文件搜索路径(. 表示当前目录)。
  • ​**-lmylib**​:链接名为 libmylib.a 的库(注意省略 lib 前缀和 .a 后缀)。

动态库(.so/.dll)

动态库(Dynamic Library)是一种在程序运行时才被加载的二进制代码库,与静态库不同,它不会被直接复制到最终的可执行文件中。动态库的核心特点是 ​​“共享”​​:多个程序可以同时使用同一个动态库,且库的更新无需重新编译主程序。

动态库的核心用途:

  1. 代码共享与减少内存占用:多个程序可以共享同一份动态库代码,减少内存占用(对比静态库,每个程序都包含库的副本)。例如,所有程序都用同一份 libc.so(C 标准库)来实现 printf()malloc(),而不是每个程序自己带一份。
  2. 运行时动态加载:程序可以在运行时按需加载动态库(如插件、游戏模组)。
  3. 跨语言调用:动态库可以通过 ​C ABI​(C 兼容接口)被其他语言(如 Python、Java)调用。

使用动态库:

  • 隐式链接(编译时绑定)  :g++ main.cpp -L. -lmylib -o program  # Linux
  • 显式链接(运行时加载):通过 dlopen(Linux)或 LoadLibrary(Windows)在代码中手动加载

何时使用静态库和动态库?

在部署便捷性上,静态库在更新后必须重新编译并重新部署整个主程序并且所有使用该静态库的程序需重新编译;而对于动态库只需替换库文件,主程序无需重新编译或部署,所有依赖该动态库的程序自动使用新版本。这是因为静态库的代码会被直接复制到最终的可执行文件里,而动态库的代码不会被复制到主程序,主程序仅记录“需要某个库”,主程序从外部加载 .so 文件​(如 /usr/lib/libmylib.so)。

在性能要求上,静态库经常用于嵌入式开发,这是因为嵌入式设备资源有限,需减少运行时依赖,同时避免动态库的 PLT 跳转开销,提升计算速度。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐