Make&GCC 简介

2018-07-16
3 min read

《An Introduction to GCC》 & 中文翻译 / 跟我一起写 Makefile / GNU Make 手册 / self wiki /

GCC 和 Make 是学习 C/C++ 都会接触到的工具。常用的 Make 语法虽然不多,但相比于现代编译工具(例如:Cmake、Bazel)而言依旧比较复杂且功能与兼容性较差,日常或工作中可以考虑使用 Cmake 替换 Make

GCC

细节请参考:《An Introduction to GCC》 & 中文翻译

g++ -std=c++17 test.cpp -o test

编译选项

  1. -Wall,Warn all,将打开所有警告选项。尽可能使用 -Wall,show all warns,GCC 默认不显示警告。也可以使用分立的命令打开不同类型的警告
    1. -Wall 并没有包含所有警告,其他警告还有
      1. -W,比如函数没有返回必要的返回值;不同类型的数据进行大小比较等。常和 -Wall 一起使用
      2. 其他,具体细节可参考上面书籍
  2. -ansi 使用标准语法,禁止不兼容标准的 GCC 扩展语法
  3. -pedantic 禁止任何不属于标准的语法,一般同时使用 -pedantic -ansi
  4. -g 让可执行文件包含调试信息
  5. –march=xxx 生成兼容某些架构 CPU 的二进制文件,比如:gcc –Wall –march=athlon ... 。使用当前选项生成的二进制文件不一定可以运行在同类型其他 CPU 中(在 Intel CPU 上编译不能再 AMD CPU 上运行),但性会好一些

优化选项

GCC 在编译时有多种不同优化手段,比如源码级优化、指令级优化等。当打开优化后,GCC 会输出一些在不是优化编译时不出现的额外警告信息

优化示例

公共子表达式消除,剔除重复计算 / 函数内嵌(inline) / 速度-空间的折衷 / 循环展开 等

for (i = 0; i < 8; i++) { y[i] = i;}

y[0] = 0;
...
y[6] = 6;
y[7] = 7;

优化级别

优化级别分 0~3 四种,默认 0,即没有任何优化

  1. O1,常见类型优化;O2,除 O1 外还加了一些指令级别的优化,一般不增加可执行文件大小;O3,使用更深入的优化,可能增加可执行文件大小
  2. Os,生成尽可能小的可执行文件
  3. -funroll-loops ,打开循环展开优化,效果不一定好

Makefile

简单用法

.PHONY: rebuild test clean 

rebuild:
	mkdir -p build
	cd build; cmake ..
	cd build; make -j7

test:
	cd build; make -j7
	cd build; ./test/test

clean:
	-rm -rf build

项目示例

细节请参考跟我一起写 Makefile,使用 CMake 可能是更好的选择

# 本 makefile 文件适合小型和中型 c/c++ 项目
# 参考:http://www.partow.net/programming/makefile/index.html
# 参考:陈皓,《跟我一起写 Makefile》,https://files.cnblogs.com/files/jiahu-Blog/Makefile.pdf

# makefile 中的等号有:`=`、`:=`、`+=` 和 `?=`,具体使用方法参考上面小册子中 ·变量中的变量·
CXX      := -c++
CXXFLAGS := -std=c++11 -pedantic-errors -Wall -Wextra -Werror
LDFLAGS  := -L/usr/lib -lstdc++ -lm
BUILD    := ./build
OBJ_DIR  := $(BUILD)/objects
T_OBJ_DIR:= $(BUILD)/test_objs
APP_DIR  := $(BUILD)/apps
TARGET   := program
T_TARGET := program_test
INCLUDE  := -Iinclude/ -Iinclude/thirdlib

# wildcard 将指定目录中所有符合规则的文件名展开为列表,文件名之间使用空格分隔
SRC      :=                     \
	$(wildcard src/module1/*.cpp) \
	$(wildcard src/module2/*.cpp) \
	$(wildcard src/module3/*.cpp) \
	$(wildcard src/*.cpp)         \

T_SRC    := $(wildcard test/*.cpp)

# 将 SRC 中满足 %.cpp 模式的文件名转化为 $(OBJ_DIR)/%.o 的形式
OBJECTS   := $(SRC:%.cpp=$(OBJ_DIR)/%.o)
T_OBJECTS := $(T_SRC:%.cpp=$(T_OBJ_DIR)/%.o)

# makefile 支持后向依赖,make 会扫描整个 makefile 然后执行,这里的 all 依赖后面的 build
all: build $(APP_DIR)/$(TARGET)

# 定义模式规则,下面的规则定义了 .cpp 文件生成 .o 文件的方式
# 第一行指明 OBJ_DIR 目录下 .o 文件的依赖项
# 第二行起始的 @ 表示执行时当前行不显示在终端
# 第三行中的 $< 表示所有依赖目标集,即所有的 %.cpp 文件; $@ 表示所有目标集,即所有的 %.o 文件
$(OBJ_DIR)/%.o: %.cpp
	@mkdir -p $(@D) 
	$(CXX) $(CXXFLAGS) $(INCLUDE) -c $< -o $@ $(LDFLAGS)

$(T_OBJ_DIR)/%.o: %.cpp
	@mkdir -p $(@D) 
	$(CXX) $(CXXFLAGS) $(INCLUDE) -c $< -o $@ $(LDFLAGS)

# makefile 的普通规则,目标、依赖和命令
$(APP_DIR)/$(TARGET): $(OBJECTS)
	@mkdir -p $(@D)
	$(CXX) $(CXXFLAGS) -o $(APP_DIR)/$(TARGET) $^ $(LDFLAGS)

# makefile 的普通规则,目标、依赖和命令
$(APP_DIR)/$(T_TARGET): $(OBJECTS) $(T_OBJECTS)
	@mkdir -p $(@D)
	$(CXX) $(CXXFLAGS) -o $(APP_DIR)/$(T_TARGET) $^ $(LDFLAGS)

# 伪目标,伪目标只是个标签,make 不会生成相应的文件
.PHONY: all build clean debug release

build:
	@mkdir -p $(APP_DIR)
	@mkdir -p $(OBJ_DIR)
	@mkdir -p $(T_OBJ_DIR)

debug: CXXFLAGS += -DDEBUG -g
debug: all

release: CXXFLAGS += -O2
release: all

test: CXXFLAGS += -DTEST_TSET
test: build $(APP_DIR)/$(T_TARGET)

clean:
	-@rm -rvf $(OBJ_DIR)/*
	-@rm -rvf $(T_OBJ_DIR)/*
	-@rm -rvf $(APP_DIR)/*
Previous C++ 14&17