gcc部分参数说明
1.GCC -M 参数
大型工程,如果必需清楚每一个源文件都包含了哪些头文件,并且在加入或删除某些头文件时,也需要一并修改Makefile,这将是一个繁重琐碎维护性极差的工作。
- -M 输出用于make系统的规则,该规则描述了源文件的依赖关系
- -MM 生成源文件依赖关系,和-M类似,但不包含标准库的头文件
- -MG 要求把缺失的头文件按存在对待,并且假定他们和源文件在同一目录下,必须和-M选项或-MM选项一起用
- -MF 将依赖关系写入指定文件
- -MD 等同于-M -MF,若给定-o选项,则输出文件名是-o指定的文件名,并添加.d后缀,若没有给定,则输入的文件名作为输出的文件名,并添加.d后缀。例gcc -E -o tmp.i -MD main.c,将生成tmp.d tmp.i
- -MMD 类似于-MD,但是输出的依赖文件中,不包含标准头文件
- -MP 生成的依赖文件里面,依赖规则中的所有.h依赖项都会在该文件中生成一个伪目标,其不依赖任何其他依赖项。防止在头文件被删除时构建失败
- -MT 在生成的依赖文件中,指定依赖规则中的目标
gcc -M -MF main.d main.c
main.c文件内容:
#include <stdio.h>
int main(int argc,char** argv) {
printf("hello,world\r\n");
return 0;
}
main.d文件内容:
main.o: main.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/x86_64-linux-gnu/bits/libc-header-start.h \
/usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
/usr/include/x86_64-linux-gnu/bits/wordsize.h \
/usr/include/x86_64-linux-gnu/bits/long-double.h \
/usr/include/x86_64-linux-gnu/gnu/stubs.h \
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/9/include/stddef.h \
/usr/lib/gcc/x86_64-linux-gnu/9/include/stdarg.h \
/usr/include/x86_64-linux-gnu/bits/types.h \
/usr/include/x86_64-linux-gnu/bits/timesize.h \
/usr/include/x86_64-linux-gnu/bits/typesizes.h \
/usr/include/x86_64-linux-gnu/bits/time64.h \
/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \
/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \
/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \
/usr/include/x86_64-linux-gnu/bits/types/__FILE.h \
/usr/include/x86_64-linux-gnu/bits/types/FILE.h \
/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \
/usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
/usr/include/x86_64-linux-gnu/bits/sys_errlist.h
gcc -c -MM -MG main.c -MP -MF main.d
main.c文件内容:
#include <stdio.h>
#include "defs.h" // defs.h文件不存在
int main(int argc,char** argv) {
printf("hello,world\r\n");
return 0;
}
main.d文件内容:
main.o: main.c defs.h
defs.h:
gcc -MF main.d -MG -MM -MP -MT main.d -MT main.o main.c
main.d文件内容:
main.d main.o: main.c defs.h
defs.h:
以上介绍gcc中的-M的大部分选项,下面结合makefile,深入理解-M选项的作用。首次编译时生成依赖文件,后续编译过程中,当任何头文件发生更改,make系统通过*.d文件可反推,依赖该.h的源文件,仅重新编译目变文件。通过这种方式,-include $(DEPS)使得Makefile能够智能地管理文件依赖,简化构建过程,并避免手动管理依赖关系的麻烦。
#make -f 打印make的内置数据库
#GNU Make版本3.81引入了一个名为.DEFAULT_GOAL的特殊变量,可用于告知如果在命令行中未指定目标,应该构建哪个目标。否则,Make会简单地使它遇到的第一个目标
.DEFAULT_GOAL := main
SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o)
DEPS=$(SRCS:.c=.d)
.PHONY: main clean
# 包含依赖关系文件
# 注释:"-"号的作用:加载错误时,会继续执行make,忽略错误
# 主要是考虑到首次make时,目录中若不存在'*.d' 文件时,加载便会产生错误而停止make的执行
-include $(DEPS)
%.o: %.c
gcc -c -g -Wall $< -o $@ -MD -MF $*.d -MP
# 注释:^:表示所有的依赖文件 $@:表示目标文件
main: $(OBJS)
gcc $^ -o $@
clean:
rm -f *.d *.o main
makefile中系统变量:
- $* 不包括扩展名的目标文件名称
- $+ 所以的依赖文件,以空格分隔
- $< 表示规则中的第一个条件
- $? 所有时间戳比目标文件晚的依赖文件,以空格分隔
- $@ 目标文件的完整名称
- $^ 所有不重复的依赖文件,以空格分隔
- $% 如果目标是归档成员,则该变量表示目标的归档成员名称
2.GCC 防止重复包含头文件
guard macro
头文件首行定义guard宏,防止重复导入相同头文件. 为了保证符号的唯一性,guard宏的名称应该基于该文件在项目目录中的完整文件路径[^1],格式是: 项目_路径_文件名_H
优点:可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含
缺点:需要自己想一个标识名,可能会重复;编译器每次都需要打开头文件才能判定是否有重复定义,编译大型项目时,编译时间相对较长
例如,foo项目中的文件foo/src/bar/baz.h应该有如下防护
#ifndef _FOO_BAR_BAZ_H_
#define _FOO_BAR_BAZ_H_
...
#endif // _FOO_BAR_BAZ_H_
pragma once
#pragma once,由编译器提供,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,用来防止某个头文件被多次include. pragma once编译器指令中,同一个文件是指物理上的一个文件,而不是指内容相同的两个文件. 如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含. GCC的3.4版本正式开始支持该编译指令.
!!! important
优点:不必再费劲想个宏名了,大型项目的编译速度也因此提高了一些
缺点:无法对一个头文件中的一段代码作pragma once声明,而只能针对文件
3.GCC 代码覆盖率

ftest-coverage
ftest-coverage告诉编译器生成用于测试覆盖率的附加文件。这些文件记录了每一行代码的执行情况, 生成.gcno文件。这些文件在程序执行前创建,并在执行后与.gcda文件配合使用,生成覆盖率报告。
具体效果:
- 创建.gcno文件,用于存储在编译时生成的覆盖率数据。
- 与-fprofile-arcs结合使用, 可以生成详细的覆盖率报告, 显示哪些行和路径被测试覆盖, 哪些没有。
fprofile-arcs
fprofile-arcs告诉编译器在代码中插入额外的指令, program flow arcs指程序执行流, 记录程序执行流。这些信息存储在生成的.gcda文件中, 供后续的分析使用。
具体效果:
- 记录每个基本块的执行次数。
- 跟踪条件分支的执行路径。
- 有助于生成分支覆盖率和路径覆盖率的详细信息。
To support cross-profiling, a program compiled with -fprofile-arcs can relocate the data files based on two environment variables
GCOV_PREFIX: contains the prefix to add to the absolute paths in the object file. Prefix can be absolute, or relative. The default is no prefix.
GCOV_PREFIX_STRIP: indicates the how many initial directory names to strip off the hardwired absolute paths. Default value is 0.
例如使用fprofile-arcs构建目标文件/user/build/foo.o, 在目标系统执行时会创建/user/build/foo.gcda文件。使用环境变量GCOV_PREFIX=/target/run和GCOV_PREFIX_STRIP=1运行, 将创建/target/run/build/foo.gcda文件
You must move the data files to the expected directory tree in order to use them for profile directed optimizations (-fprofile-use), or to use the gcov tool.
By default, the .gcda files are also stored in the same directory as the object file, but the GCC -fprofile-dir option may be used to store the .gcda files in a separate directory.

4.GCC 代码优化
基于运行数据优化
fprofile-generate用于生成代码的执行性能分析信息,以便在后续的编译过程中进行优化。编译器会在生成的二进制文件中插入额外的代码, 用于记录程序在运行时的行为信息。这些信息包括哪些代码路径被执行过、被执行的频率等。程序运行时会在当前目录或指定的目录中生成一个.gcda文件, 这个文件包含了程序的执行分析数据。在生成了性能分析数据之后,可以使用profile-use选项重新编译程序, 编译器会利用之前收集的执行分析数据对代码进行优化。这种优化基于实际运行时的行为,可以使编译器更好地优化代码的性能。
- 优化函数内联
- 优化分支预测
- 优化循环展开
- 重新排序代码以提高缓存命中率
gcc -fprofile-generate -o my_program my_program.c
./my_program
gcc -fprofile-use -o my_program_optimized my_program.c
5.GCC 除去注释
gcc -fpreprocessed -dD -E -P test.c