Cursor blinking

C++ 程序编译流程

C++ 基础|字数 1,700|阅读时长≈ 5 分钟

C++ 程序编译流程

C++ 程序的编译流程通常包括以下几个步骤:

  • 预处理(Preprocessing)
  • 预处理阶段处理以 # 开头的预处理指令,如 #include、#define 等。
  • 展开头文件,将头文件内容插入源文件中。
  • 生成预处理后的代码文件,通常以 .i 或 .ii 为扩展名。
  • 编译(Compilation)
  • 编译阶段将预处理后的代码翻译成汇编代码。
  • 语法分析、语义分析、优化等步骤在这个阶段完成。
  • 生成汇编代码文件,通常以 .s 为扩展名。
  • 汇编(Assembly)
  • 汇编阶段将汇编代码转换为目标文件。
  • 汇编器将汇编代码翻译成机器码指令。
  • 生成目标文件,通常以 .o 或 .obj 为扩展名。
  • 链接(Linking)
  • 链接阶段将多个目标文件和库文件组合在一起,生成可执行文件。
  • 解析外部符号引用,解决符号地址。
  • 将目标文件中的代码和库文件中的代码整合在一起,生成可执行文件。
  • 可执行文件(Executable)
  • 最终生成的可执行文件包含了机器码指令,可以在特定平台上运行。

这些步骤通常由编译器和链接器来完成。在实际编译过程中,可以直接使用集成开发环境(IDE)或者命令行工具来完成整个编译流程。常见的 C++ 编译器包括 GNU Compiler Collection (GCC)、Clang 等。

编译流程示例

我们使用一个简单的 C++ 程序示范编译流程,对应的目录结构和代码如下:

Code
/** * 目录结构  * ├── hello.cpp├── include│   └── log.h└── src    └── log.cpp*/  // hello.cpp 文件#include<iostream>#include "include/log.h" int main(){    std::cout << "Hello World!" << std::endl;    log("This is a log message.");}  // log.h 文件#include <iostream>#include <ctime> using namespace std;void log(const string& message);  // log.cpp 文件#include "../include/log.h" void log(const string& message) {        // 获取当前日期和时间    time_t now = time(0);    struct tm *localTime = localtime(&now);     // 格式化日期和时间    char buffer[80];    strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", localTime);     // 输出日志信息    cout << "[" << buffer << "] " << message << endl;}
  1. 预处理
Code
g++ -E -I include/ hello.cpp -o hello.i
Code
.├── hello.cpp├── hello.i        // 预处理后生成 .i 文件「文本文件」├── include│   └── log.h└── src    └── log.cpp

使用 g++ 编译器,选项 "-E" 表示只进行预处理,而不进行编译、汇编和链接。预处理阶段是 C++ 编译过程中的第一步,它包括宏展开、头文件包含、条件编译等操作。在预处理这一步,代码注释直接被忽略,不会进入到后续的处理中。使用 "-E" 选项可以帮助你调试宏定义、查看预处理后的代码,或者检查编译器对代码的解释。

  1. 编译
Code
g++ -S -I include/ hello.cpp -o hello.s
Code
.├── hello.cpp├── hello.i├── hello.s        // 编译之后生成汇编代码 .s 文件「文本文件」├── include│   └── log.h└── src    └── log.cpp

使用 g++ 编译器,选项 "-S" 表示生成汇编语言代码而不进行汇编和链接,即将 C++ 源代码编译成汇编代码而不生成可执行文件。这个选项的作用是将 C++ 源代码转换为对应的汇编语言代码,方便程序员查看和分析代码在汇编层面的实现细节。

  1. 汇编
Code
g++ -c -I include/ hello.s -o hello.o
Code
.├── hello.cpp├── hello.i├── hello.o        // 将汇编文件转换成机器码文件「二进制格式文件」├── hello.s├── include│   └── log.h└── src    └── log.cpp

使用 g++ 编译器,选项 "-c" 表示编译源文件但不进行链接。具体来说,使用 "-c" 选项会告诉编译器只进行编译阶段,生成目标文件(object file),而不进行链接生成可执行文件。

这个目标文件包含了源文件编译后的机器代码,但还没有被链接到其他目标文件或库中。

通常情况下,在大型项目中,源代码会被分成多个文件,每个文件编译成一个目标文件,然后这些目标文件再被链接在一起生成最终的可执行文件。使用"-c"选项可以让你在编译大型项目时分阶段进行,先生成目标文件,然后再进行链接。

所以这里再对 log.cpp 文件进行编译

Code
gcc -c -I include/ src/log.cpp -o src/log.o
Code
.├── hello.cpp├── hello.i├── hello.o├── hello.s├── include│   └── log.h└── src    ├── log.cpp    └── log.o
  1. 链接
Code
g++ src/log.o hello.o -o hello.out
Code
.├── hello.cpp├── hello.i├── hello.o├── hello.out        // 链接后生成可执行文件├── hello.s├── include│   └── log.h└── src    ├── log.cpp    └── log.o

使用 g++ 编译,选项 "-o" 用于指定生成的可执行文件的名称。通过 "-o" 选项,你可以指定编译器将编译后的目标文件链接成的可执行文件的名字。如果你不使用 "-o" 选项,编译器通常会生成一个默认的可执行文件,例如 a.out。

Code
// 执行 ./hello.out // 输出Hello World![2024-04-03 21:37:09] This is a log message.

你可能会遇到的问题

linker command failed with exit code 1 (use -v to see invocation)
  1. 请检查编译器是否使用的是 clang++ 、g++ ,如果使用 gcc、clang 编译器可能会遇到上面的错误
  2. 检查头文件 .h 引入路径是否正确

补充说明

关于 GNU、GCC、gcc、g++ 的简要解释

  • GNU (GNU's Not Unix)
  • GNU 是一个开源项目,旨在创建一个完全自由的操作系统。
  • GNU 项目开发了许多开源软件,包括 GCC 编译器、Emacs 文本编辑器、GDB 调试器等。
  • GCC (GNU Compiler Collection)
  • GCC 是 GNU 编译器套件的缩写,是一个开源的编译器集合,支持多种编程语言,如 C、C++、Objective-C、Fortran 等。
  • GCC 提供了一组编译器和工具,用于将源代码编译成可执行文件或库。它是一个广泛使用的编译器套件,适用于多种平台和操作系统。
  • gcc
  • gcc 是 GCC 编译器套件中专门用于编译 C 代码的编译器。
  • gcc 命令是用于编译 C 语言源代码文件的命令。当你使用 gcc 命令编译一个 C 语言源文件时,它将调用 C 语言编译器来编译该源文件,并生成可执行文件。
  • g++
  • g++ 是 GCC 编译器套件中专门用于编译 C++ 代码的编译器。
  • 通过 g++ 命令可以调用 GCC 中的 C++ 编译器来编译 C++ 代码,简化了编译 C++ 代码的过程。

g++ 指令

g++ 是 GNU 编译器套件(GCC)中用于编译 C++ 程序的编译工具,下面是一些常用的 g++ 指令:

指令描述
编译源文件g++ -o file.cpp output编译 file.cpp 文件并生成可执行文件 output。
指定 C++ 标准g++ -std=c++11 file.cpp -o output指定使用 C++11 标准编译 file.cpp 文件。
生成调试信息g++ -g file.cpp -o output生成可执行文件时包含调试信息,便于调试程序。
优化编译g++ -O2 file.cpp -o output使用优化级别 2 进行编译,提高程序性能。
预处理g++ -E file.cpp -o output.i对 file.cpp 文件进行预处理,生成预处理后的文件 output.i。
显示编译过程g++ -v file.cpp -o output显示编译过程的详细信息。
链接外部库g++ file.cpp -o output -l链接名为 lib.so 或 lib.a 的外部库。
指定头文件路径g++ -I/path/to/include file.cpp -o output指定头文件的搜索路径。

参考文档

Feature

  • 对比源文件与编译后文件大小,反编译编译后文件为什么变大了
  • 编译、链接的错误,和对应的错误提示
  • 不使用的函数,会被编译吗
  • 函数的签名
  • 动态链接、静态链接