Makefile学习(三)- CFLAGS和LDFLAGS
一、简介
在 Makefile 中,CFLAGS
和 LDFLAGS
是两个约定俗成的变量,分别用于传递编译阶段和链接阶段的参数,二者分工明确,共同控制程序的构建过程。
二、CFLAGS
CFLAGS(Compiler Flags),即编译阶段的参数。作用是给编译器(如 gcc
、clang
)传递参数,控制 .c
源文件编译为 .o
目标文件的过程。
常见参数如下:
-I/path | 指定头文件(.h )的搜索路径(非标准路径) |
-Wall | 开启所有警告信息,仅提示警告,不影响编译过程 |
-Werror | 将警告视为错误,检测到警告会立即终止编译。通常与-Wall一起使用,如果单独使用通常不会报错(默认启用的警告极少) |
-O2 | 开启二级优化(平衡编译速度和程序性能) |
-g | 生成调试信息(用于 gdb 调试) |
-c | 只编译不链接(生成 .o 文件,Makefile 中常用) |
-D宏名 | 定义宏(如 -DDEBUG 开启调试模式) |
-I
用于指定头文件(.h)的搜索路径,告诉编译器在哪些目录中查找源代码中 #include
引用的头文件。
建设项目结构如下:
project/
├── a.c
└── a.h
编写如下程序
### a.c#include <stdio.h>
#include "a.h"int main(void)
{printf("this is a\r\n");printf("A = %d\r\n",A);return 0;
}
#### a.h#ifndef A_H
#define A_H#define A 1#endif
### MakefileCC := gcc # 指定编译器
SRC := a.c # 指定源文件
OBJ := $(SRC:.c=.o) # 指定目标文件
.PHONY: allall: test @echo "所有目标构建完成"test: $(OBJ)@$(CC) -o $@ $^ %.o: %.c@$(CC) -o $@ -c $<clean:@rm -f $(OBJ) test
此时make一下。没有问题。
此时修改项目结构
project/
├── a.c
└── include/└── a.h
直接make一下。
提示.h文件未找到。此时在Makefile中增加CFLAGS -I参数。
CC := gcc # 指定编译器
SRC := a.c # 指定源文件
OBJ := $(SRC:.c=.o) # 指定目标文件
CFLAGS = -I./include # 指定头文件搜索路径为当前目录下的include文件
.PHONY: allall: test @echo "所有目标构建完成"test: $(OBJ)@$(CC) -o $@ $^ %.o: %.c@$(CC) $(CFLAGS) -o $@ -c $<clean:@rm -f $(OBJ) test
重新make。正常。
-Wall
用于开启大部分常见的警告信息(虽然名字里有 "all",但并非所有警告,而是 GCC 认为最有用的一批警告)。它的主要作用是让编译器在编译过程中检查代码中潜在的问题,并给出警告提示,帮助开发者发现可能的错误、不规范的写法或逻辑漏洞,但不会阻止编译过程。
这里定义一个全局变量,但是不使用。
### a.c#include <stdio.h>static int aa;int main(void)
{printf("this is a\r\n");return 0;
}
CC := gcc # 指定编译器
SRC := a.c # 指定源文件
OBJ := $(SRC:.c=.o) # 指定目标文件
.PHONY: allall: test @echo "所有目标构建完成"test: $(OBJ)@$(CC) -o $@ $^ %.o: %.c@$(CC) $(CFLAGS) -o $@ -c $<clean:@rm -f $(OBJ) test
make一下,并没有看到有报错。
在makefile中加入-Wall参数。
CFLAGS = -Wall
再次Make。可以看到,有报警提示aa未使用,但是还是继续完成了编译和链接的动作。
-Werror
该参数是将所有已启动的警告,强制转化为错误,并立即终止编译过程,不生成目标文件.o或可执行文件。
还是刚才的例子,在makefile中增加-werror参数
CFLAGS = -Wall -Werror
可以看到,编译直接报错,并且停止了继续编译。
-O2
-O2是一个优化级别选项,用于启动中等强度的代码优化,在编译速度和程序性能之间取得平衡,是实际开发中最常用的优化选项之一。
-O2会让编译器对代码进行一系列优化处理,目标是提高程序的运行速度(减少执行时间)和降低内存占用,但会略微增加编译时间(因为优化过程需要更多计算)。
与不优化(默认 -O0
)相比,-O2
启用的典型优化包括:
- 常量折叠:计算编译期可知的常量表达式(如
3+5
直接替换为8
) - 循环优化:循环展开、循环变量递增优化、消除无效循环
- 函数内联:将小型函数的代码直接嵌入调用处,减少函数调用开销
- 指令重排:调整指令执行顺序,利用 CPU 流水线提高效率
- 死代码消除:移除永远不会执行的代码(如
if (0) { ... }
中的内容) - 寄存器优化:更高效地使用 CPU 寄存器,减少内存访问
GCC 提供多个优化级别,常用的有
-O0(默认) | 无优化,编译速度最快,适合调试(代码与源码对应性好) |
-O0 | 轻度优化,优化编译时间,对执行性能提升有限 |
-O0 | 中度优化,平衡编译时间和运行性能(推荐在发布版本中使用) |
-O0 | 高度优化,启用更多激进优化(如循环向量化),可能导致程序体积增大或兼容性问题 |
-Os | 优化代码体积,适合嵌入式等存储空间有限的场景 |
注意:启用 -O2
后,编译器会重排代码,可能导致调试时(如 gdb
)源码行号与实际执行位置不匹配,因此调试阶段通常用 -O0 -g
(无优化 + 调试信息)。
-O2
是大多数生产环境的默认选择,既保证了性能提升,又避免了 -O3
可能带来的风险。
-g
-g
是用于生成调试信息的编译选项,这些信息会被嵌入到目标文件(.o
)或可执行文件中,供调试工具(如 gdb
、lldb
等)使用,帮助开发者定位代码中的错误。
当使用 -g
选项时,编译器会在生成的二进制文件中加入:
- 源代码的行号与二进制指令的映射关系
- 变量名、函数名等符号信息
- 数据类型、结构体定义等类型信息
这些信息让调试工具能够将二进制指令 “翻译” 回对应的源代码,实现单步执行、变量查看、断点设置等调试功能。
-c
-c
是一个编译选项,用于指定只进行编译(Compile)过程,不执行链接(Link),最终生成目标文件(.o
文件)而非可执行文件。
C/C++ 程序的构建通常分为两步:
- 编译(Compile):将
.c
源文件转换为.o
目标文件(二进制中间文件,包含机器码和符号信息)。 - 链接(Link):将多个
.o
目标文件及依赖的库文件合并,生成最终的可执行文件。
-c
的作用是只执行第一步,停止在生成 .o
文件的阶段,不进行后续的链接操作。
-D
-D
选项用于在编译时定义宏,相当于在代码中添加 #define 宏名
或 #define 宏名=值
的效果,但无需修改源代码,直接通过编译参数控制宏的定义。
CC := gcc # 指定编译器
SRC := a.c b.c # 指定源文件
# CFLAGS := -DRELEASE
CFLAGS := -DDEBUG
TARGET := testOBJ := $(SRC:.c=.o).PHONY: all cleanall: $(TARGET) @echo "所有目标构建完成"$(TARGET): $(OBJ)@$(CC) -o $@ $^ $(CFLAGS)%.o: %.c@$(CC) $(CFLAGS) -o $@ -c $<clean: @rm -f $(OBJ) $(TARGET)
int main(void)
{printf("this is a\r\n");#ifdef DEBUGprintf("DEBUG is defined\r\n");
#endif#ifdef RELEASEprintf("RELEASE is defined\r\n");
#endifreturn 0;
}
通过修改CFLAGS := -DRELEASE或CFLAGS := -DDEBUG来切换a.c中程序运行的位置。
三、LDFLAGS
LDFLAGS(Linker Flags),即链接阶段参数。作用是给链接器传递参数,控制 .o
目标文件链接为可执行文件或库的过程。
常见参数如下:
-L/path | 指定库文件(.so /.a )的搜索路径(非标准路径) |
-l库名 | 链接指定的库(如 -lm 链接数学库 libm ) |
-Wl,-rpath=/path | 指定程序运行时的库搜索路径(解决动态库找不到的问题) |
-static | 强制静态链接(生成不依赖动态库的独立程序) |
-L
在 Makefile 中,LDFLAGS
是用于指定链接阶段参数的变量,而 -L
是其中一个重要的链接选项,用于指定库文件的搜索路径。全称是 "library search path",用于告诉链接器在哪些额外目录中搜索需要的库文件(包括静态库 .a
和动态库 .so
)。
默认情况下,链接器只会在系统标准库目录(如 /usr/lib
、/usr/local/lib
)中搜索库文件。如果你的库文件放在自定义目录(如项目的 ./lib
文件夹),就需要用 -L
指定路径。
若有多个库目录,可通过多个 -L
指定
LDFLAGS := -L./lib -L./third_party/lib # 同时搜索 ./lib 和 ./third_party/lib
编译链接时,链接器会先在 -L
指定的目录中搜索库文件,再搜索系统标准目录,确保能找到自定义库文件,避免出现 cannot find -lxxx
(找不到库)的错误。
CFLAGS -I和LDFLAGS -L的区别
-I./include
:编译阶段使用,指定头文件(.h
)的搜索路径。
-L./lib
:链接阶段使用,指定库文件(.a
/.so
)的搜索路径。
-l
-l
(小写 L)是用于指定需要链接的库文件的选项,通常与 -L
(指定库路径)配合使用,告诉链接器要将哪些库文件合并到最终的可执行程序中。
-l
后面直接跟库名(需省略库文件的前缀 lib
和后缀 .a
或 .so
):
- 链接静态库
libmath.a
→ 用-lmath
- 链接动态库
liblog.so
→ 用-llog
链接器会根据平台默认规则优先选择动态库(.so
),若找不到则尝试静态库(.a
)。
在 Makefile 中,通常将 -l
选项放在 LDLIBS
变量中(专门存放库名参数),与 LDFLAGS
(存放路径参数 -L
)分开管理,例如
# 库路径:告诉链接器去哪里找库(-L选项)
LDFLAGS := -L./lib # 自定义库目录
# 库名:告诉链接器需要链接哪些库(-l选项)
LDLIBS := -lmath -llog # 对应 libmath.a/libmath.so 和 liblog.a/liblog.so
-WL
在 GCC 编译链接中,-WL
是一个链接器参数传递选项,用于将其后的参数直接传递给底层的链接器(通常是 ld
),而不是由 GCC 本身处理。它的核心作用是 “桥接” GCC 和链接器,让开发者能直接控制链接器的行为。
GCC 作为编译器前端,会先处理编译逻辑(.c
→ .o
),再调用链接器(ld
)完成最终的链接。大部分链接相关参数(如 -L
、-l
)GCC 能自动转发给链接器,但链接器特有的参数(如动态库路径配置、版本号控制)需要通过 -WL
显式传递,格式为:-WL,参数1,参数2,...
(多个参数用逗号分隔,无空格)。
-static
在 GCC 编译中,-static
是一个链接选项,用于指定静态链接方式 —— 将程序依赖的所有库文件(包括系统库)全部打包到最终的可执行文件中,生成一个不依赖外部动态库(.so
)的独立可执行程序。
四、总结
维度 | CFLAGS | LDFLAGS |
---|---|---|
作用阶段 | 编译(.c → .o ) | 链接(.o → 可执行文件) |
处理对象 | 源文件和头文件 | 目标文件和库文件 |
核心参数 | -I (头文件路径)、-Wall (警告) | -L (库路径)、-l (链接库) |
依赖工具 | 编译器(如 gcc 的编译阶段) | 链接器(如 ld 或 gcc 的链接阶段) |