GCC 和 G++ 简介
GCC(GNU Compiler Collection)和 G++ 是开源的编译工具,广泛用于 C、C++ 等多种编程语言的编译和构建。它们是 GNU 项目的一部分,支持多种平台。
1. GCC 和 G++ 的区别
| 特性 | GCC | G++ |
|---|---|---|
| 主要用途 | 编译 C 代码和其他语言(如 Fortran、Ada)。 | 专门用于编译 C++ 代码。 |
| 默认语言 | 默认编译器为 C。 | 默认编译器为 C++。 |
| 调用方式 | 手动指定 gcc 支持的语言。 | 自动将输入文件识别为 C++。 |
| 链接行为 | 默认使用 C 的标准库 libc。 | 自动链接 C++ 的标准库 libstdc++。 |
2. GCC
功能
- 多语言支持:可以编译 C、C++、Fortran、Ada、Go 等多种语言。
- 通用性:作为一组编译工具的核心,提供灵活的选项和多阶段优化。
- 跨平台性:支持多种操作系统和架构。
示例
编译 C 程序:
gcc -Wall main.c -o main //注意加上-Wall可以更好的检查一些错误警告
手动编译 C++ 程序(但需要显式链接 C++ 标准库):
gcc -Wall main.cpp -lstdc++ -o main
3. G++
功能
- 专为 C++ 语言设计。
- 自动链接 C++ 标准库(
libstdc++),无需显式指定。 - 默认开启 C++ 标准支持,支持现代 C++ 标准(如 C++11、C++17、C++20)。
示例
编译 C++ 程序:
g++ -Wall main.cpp -o main
支持不同的 C++ 标准:
g++ -std=c++17 main.cpp -o main
4. GCC 和 G++ 的关系
- 底层一致性:GCC 和 G++ 使用的是同一个编译器核心,G++ 是 GCC 的一个专门用于 C++ 的包装。
- 差异在于默认行为:GCC 默认处理 C 代码,而 G++ 自动启用 C++ 编译器及标准库支持。
5. 常见选项
无论是 GCC 还是 G++,它们支持许多相同的编译选项,例如:
-c:只编译,不链接。-o:指定输出文件名。-Wall:启用所有常见警告。-g:生成调试信息。-O2:启用优化。
6.优化方式
在编译器优化中,CSE(Common Subexpression Elimination)和 FL(Function Inlining)是常见的优化技术,它们可以提高代码的执行效率和运行速度。以下是对两者的简单介绍:
CSE:公共子表达式消除(Common Subexpression Elimination)
定义
公共子表达式消除是一种通过减少重复计算来优化代码的技术。它检测程序中出现的相同子表达式,并将其替换为单次计算结果的引用,从而节省重复计算的开销。
优化示例
未优化的代码:
int a = (x + y) * (x + y);
在该代码中,(x + y) 是一个公共子表达式。
优化后的代码:
int t = x + y;
int a = t * t;
优点
- 减少重复计算,提高执行效率。
- 节省 CPU 时间。
限制
- 增加了临时变量的使用,可能稍微增加内存消耗。
- 需要编译器静态分析以确保子表达式没有副作用(如函数调用或全局变量修改)。
FL:函数内联(Function Inlining)
定义
函数内联是通过将函数调用替换为函数体的方式来消除函数调用的开销。它直接将被调用的函数代码插入调用点,从而减少函数调用的开销。
优化示例
未优化的代码:
int square(int x) {
return x * x;
}
int main() {
int result = square(5);
return result;
}
优化后的代码(内联展开):
int main() {
int result = 5 * 5; // 函数调用被直接替换为函数体
return result;
}
优点
- 减少函数调用的开销(如参数传递和栈操作)。
- 可能进一步触发其他优化(如循环展开或常量传播)。
限制
- 内联会增加代码体积(代码膨胀),尤其是频繁调用的大函数。
- 对递归函数无效,通常不会内联递归调用。
CSE 和 FL 的对比
| 特性 | CSE | FL |
|---|---|---|
| 全称 | Common Subexpression Elimination | Function Inlining |
| 作用对象 | 表达式 | 函数调用 |
| 优化目标 | 减少重复计算,节省时间 | 消除函数调用开销,提高性能 |
| 可能的副作用 | 增加临时变量数量 | 增加代码体积 |
| 适用场景 | 表达式中存在重复计算 | 函数调用频繁且函数体较小 |
总结
-
CSE(公共子表达式消除):针对代码中的重复表达式优化,减少计算次数。
-
FL(函数内联):针对函数调用优化,减少调用开销。
- 编译器通常会根据代码的复杂性、函数体大小和优化级别(如
-O1、-O2、-O3)自动应用这些技术,而开发者也可以通过写高效代码配合编译器的优化策略。 - 使用
gcc -Wall -O2 -c hello.c得到的文件hello.o,大小不一定比-O1的优化要小,有可能会大于O1优化的代码,并且运行时间也有可能会比-O1的时间长。
- 编译器通常会根据代码的复杂性、函数体大小和优化级别(如
7.gprof工具和gcov工具
gprof 和 gcov 是 GNU 工具链中的两个分析工具,分别用于 性能分析 和 代码覆盖率分析。以下是它们的详细用法和区别:
gprof:性能分析工具
作用
gprof 用于分析程序的性能,提供以下信息:
- 程序运行过程中函数调用的次数。
- 每个函数执行的总时间和调用次数分布。
- 调用图信息(函数之间的调用关系)。
使用步骤
-
编译程序(启用性能分析)
使用-pg选项编译和链接程序:gcc -pg program.c -o program -
运行程序
执行编译生成的程序,会生成性能数据文件(gmon.out):./program -
生成性能分析报告
使用gprof命令分析gmon.out文件,输出报告:gprof program gmon.out > report.txt -
查看分析结果
打开report.txt,可以看到以下信息:- Flat Profile:每个函数的执行时间和调用次数。
- Call Graph:函数之间的调用关系及时间分布。
示例输出
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
50.0 0.50 0.50 2 250.00 250.00 foo
50.0 1.00 0.50 1 500.00 500.00 bar
foo和bar是函数名称。calls是函数调用次数。self seconds是该函数本身运行所花费的时间。
gcov:代码覆盖率工具
作用
gcov 用于检测程序的代码覆盖率,帮助开发者了解:
- 哪些代码被执行了。
- 哪些代码未被执行(死代码)。
- 每行代码的执行次数。
使用步骤
-
编译程序(启用覆盖率分析)
使用-fprofile-arcs和-ftest-coverage选项编译程序:gcc -fprofile-arcs -ftest-coverage program.c -o program -
运行程序
执行编译生成的程序,会生成以下文件:program.gcda:覆盖率统计数据文件。program.gcno:源代码结构文件。
./program -
生成覆盖率报告
使用gcov生成覆盖率报告:gcov program.c会生成一个
program.c.gcov文件,包含每行代码的执行次数信息。 -
查看覆盖率报告
打开program.c.gcov文件,可以看到每行代码的覆盖信息:-: 0:Source:program.c -: 1:#include <stdio.h> -: 2: 1: 3:int main() { 1: 4: printf("Hello, World!\n"); 1: 5: return 0; -: 6:}1:表示该行代码被执行 1 次。-:表示该行是注释或不可执行代码。#####:表示该行从未被执行。- 可以使用
grep "#####" *.gcov查看没有被执行的代码。
覆盖率百分比
可以结合 lcov 工具生成 HTML 格式的覆盖率报告,方便查看总覆盖率。
gprof 和 gcov 的对比
| 特性 | gprof | gcov |
|---|---|---|
| 主要用途 | 性能分析:关注函数运行时间和调用次数 | 覆盖率分析:关注代码的执行情况 |
| 输出内容 | 函数调用图,执行时间分布 | 每行代码的执行次数及覆盖率信息 |
| 适用场景 | 优化程序性能 | 找出未被测试的代码 |
| 生成文件 | gmon.out 文件 | .gcda 和 .gcno 文件 |
| 编译选项 | -pg | -fprofile-arcs -ftest-coverage |
总结
- 使用
gprof优化程序的性能瓶颈,关注函数的时间和调用关系。 - 使用
gcov确保代码的测试覆盖率,查找未被执行的代码部分。 - 两者可以结合使用,优化程序的性能和质量。

被折叠的 条评论
为什么被折叠?



