前言
自接触C语言以来,小demo直接gcc,大项目的Makefile都是框架里自带的,一般都是按需修修补补,具体的规则总是摸不清搞不懂!当自己搭个小项目,手搓一个Makefile真是费劲,根本写不出来,因此书此博客,以后来ctrl+c
。
本文不会详细展开如何编写一个Makefile。如想了解种种细节,请参考下面这个非常详细的教程,包含几乎GNU make的Makefile的所有细节:
而本文包含以下内容:
- makefile小模板
- gcc指令
Makefile小模板
适用于纯 C 语言
# 指令编译器和选项
CC=gcc
CFLAGS=-Wall -std=gnu99
# 目标文件
TARGET=main
SRCS = main1.c \
main2.c \
main3.c
INC = -I./
OBJS = $(SRCS:.c=.o)
$(TARGET):$(OBJS)
$(CC) -o $@ $^
clean:
rm -rf $(TARGET) $(OBJS)
%.o:%.c
$(CC) $(CFLAGS) $(INC) -o $@ -c $<
注意:Makefile有个规则就是命令行是以tab键开头,4个空格或其他则会报错:
Makefile:2: *** missing separator。stop
- 相比于单个文件和多个文件的makefile,通过变量
INC
制定了头文件路径。头文件路径之间通过空格隔开。 - 编译规则
%.o:%.c
中加入了头文件参数$(CC) $(CFLAGS) $(INC) -o $@ -c $<
, - 单个文件和多个文件的makefile相比增加了头文件路径参数。
SRCS
变量中,文件较多时可通过“\”
符号续行。$@
--代表目标文件$^
--代表所有的依赖文件$<
--代表第一个依赖文件(最左边的那个)。
适用于 C/C++ 混合编译
目录结构如下:
httpserver
│ main.cpp
│ Makefile
└─────inc
│ │ mongoose.h
│ │ http_server.h
│
──────src
│ │ http_server.cpp
│ │ mongoose.c
│ │ ...
Makefile 如下:
CC=gcc
CXX=g++
# 编译器在编译时的参数设置,包含头文件路径设置
CFLAGS:=-Wall -O2 -g
CFLAGS+=-I $(shell pwd)/inc
CXXFLAGS:=-Wall -O2 -g -std=c++11
CXXFLAGS+=-I $(shell pwd)/inc
# 库文件添加
LDFLAGS:=
LDFLAGS+=
# 指定源程序存放位置
SRCDIRS:=.
SRCDIRS+=src
# 设置程序中使用文件类型
SRCEXTS:=.c .cpp
# 设置运行程序名
PROGRAM:=httpserver
SOURCES=$(foreach d,$(SRCDIRS),$(wildcard $(addprefix $(d)/*,$(SRCEXTS))))
OBJS=$(foreach x,$(SRCEXTS),$(patsubst %$(x),%.o,$(filter %$(x),$(SOURCES))))
.PHONY: all clean distclean install
%.o: %.c
$(CC) -c $(CFLAGS) -o $@ $<
%.o: %.cxx
$(CXX) -c $(CXXFLAGS) -o $@ $<
$(PROGRAM): $(OBJS)
ifeq ($(strip $(SRCEXTS)),.c)
$(CC) -o $(PROGRAM) $(OBJS) $(LDFLAGS)
else
$(CXX) -o $(PROGRAM) $(OBJS) $(LDFLAGS)
endif
install:
install -m 755 -D -p $(PROGRAM) ./bin
clean:
rm -f $(shell find -name "*.o")
rm -f $(PROGRAM)
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(PROGRAM)
all:
@echo $(OBJS)
gcc指令
一步到位
gcc main.c -o main
多个程序文件的编译
gcc main1.c main2.c -o main
预处理
gcc -E main.c -o main.i
或
gcc -E main.c
gcc的-E选项,可以让编译器在预处理后停止,并输出预处理结果。
编译为汇编代码
预处理之后,可直接对生成的test.i文件编译,生成汇编代码:
gcc -S main.i -o main.s
gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件。
汇编
对于上文中生成的汇编代码文件test.s,gas汇编器负责将其编译为目标文件,如下:
gcc -c main.s -o main.o
连接
gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
对于上一小节中生成的main.o,将其与C标准输入输出库进行连接,最终生成可执行程序main。
检错
参数-Wall
,使用它能够使GCC产生尽可能多的警告信息。
gcc -Wall main.c -o main
在编译程序时带上-Werror
选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:
gcc -Werrormain.c -o main
创建动态链接库
生成生成o文件
gcc -c -fPIC add.c
//这里一定要加上-fPIC选项,目的使库不必关心文件内函数位置
再编译
gcc -shared -fPIC -o libadd.so add.o
库文件连接
开发软件时,完全不使用第三方函数库的情况是比较少见的,通常来讲都需要借助许多函数库的支持才能够完成相应的功能。从程序员的角度看,函数库实际上就是一些头文件(.h
)和库文件(so、或lib、dll
)的集合。虽然Linux
下的大多数函数都默认将头文件放到/usr/include/
目录下,而库文件则放到/usr/lib/
目录下;但也有的时候,我们要用的库不在这些目录下,所以GCC在编译时必须用自己的办法来查找所需要的头文件和库文件。
额外补充:Linux需要连接so库文件(带软连接),可以完完整整的复制到/usr/include/
或/usr/lib/
目录下,使用 cp -d * /usr/lib/
命令,然后别忘记再运行 ldconfig
。
其中inclulde文件夹的路径是/home/test/include
,lib文件夹是/home/test/lib
,lib文件夹中里面包含二进制so文件libtest.so
首先要进行编译main.c为目标文件,这个时候需要执行:
gcc –c –I /home/test/include main.c –o main.o
最后把所有目标文件链接成可执行文件:
gcc –L /home/test/lib -ltest main.o –o main
默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static选项,强制使用静态链接库。
gcc –L /home/test/lib -static -ltest main.o –o main
静态库链接时搜索路径顺序:
ld
会去找GCC命令中的参数-L- 再找gcc的环境变量
LIBRARY_PATH
- 再找内定目录
/lib
、/usr/lib
、/usr/local/lib
这是当初compile gcc时写在程序内的
动态链接时、执行时搜索路径顺序:
- 编译目标代码时指定的动态库搜索路径
- 环境变量
LD_LIBRARY_PATH
指定的动态库搜索路径 - 配置文件
/etc/ld.so.conf
中指定的动态库搜索路径 - 默认的动态库搜索路径
/lib
- 默认的动态库搜索路径
/usr/lib
相关环境变量:
LIBRARY_PATH
环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH
环境变量:指定程序动态链接库文件搜索路径