二话不说,先上一个Makefile的源码。
基于下述的Makefile,可以直接执行命令:
编译: make or make -f Makefile all
清除: make clean or make -f Makefile clean
CC = g++
CUR_PATH = $(PWD)
FLAGS = -std=c++11 -O2 -W -Wall
FLAGS += -I/home/project/opencv/build/ -I/home/project/opencv/include/
FLAGS += -I/home/project/opencv/modules/calib3d/include
#FLAGS += ... 其他必要的头文件的路径
LDFLAGS = -L../libopencv_calib3d.a
LDFLAGS += ./libopencv_core.a
LDFLAGS += ./libopencv_dnn.a
LDFLAGS += ./libopencv_core.a
LDFLAGS += ./libopencv_features2d.a
LDFLAGS += ./libopencv_flann.a
LDFLAGS += ./libopencv_gapi.a
LDFLAGS += ./libopencv_highgui.a
LDFLAGS += ./libopencv_imgcodecs.a
LDFLAGS += ./libopencv_imgproc.a
LDFLAGS += ./libopencv_ml.a
LDFLAGS += ./libopencv_objdetect.a
LDFLAGS += ./libopencv_photo.a
LDFLAGS += ./libopencv_stitching.a
LDFLAGS += ./libopencv_video.a
LDFLAGS += ./libopencv_videoio.a
LDFLAGS += ./libade.a
LDFLAGS += ./libIlmImf.a
LDFLAGS += ./libippicv.a
LDFLAGS += ./libippiw.a
LDFLAGS += ./libittnotify.a
LDFLAGS += ./liblibopenjp2.a
LDFLAGS += ./liblibprotobuf.a
LDFLAGS += ./liblibtiff.a
LDFLAGS += -ldl -lm -lpthread -lrt
TARGET = main
SMP_SRCS = test_resize.cpp
OBJS := $(SMP_SRCS:%.c=%.o)
CFLAGS += $(FLAGS)
MPI_LIBS = $(LDFLAGS)
.PHONY : clean all
all: $(TARGET)
$(TARGET):$(OBJS)
@$(CC) $(CFLAGS) -o $@ $^ -Wl,--start-group $(MPI_LIBS) \
Wl,--end-group,-gc-sections,-g
@echo "start the compilexxxxxxxxxxxxxxxxxxxxxxx"
@echo $(MPI_LIBS)
@echo $@
@echo $^
@echo $(CFLAGS)
@echo $(OBJS)
@echo $(TARGET)
clean:
@rm -f *.o main
1. 分析Makefile的入口在 .PHONY的位置。
Makefile的target默认是文件,何为target?拿make clean举例,clean就可以认为是target。但是这里的clean并不希望是文件,而希望是Makefile中clean标识,所以使用.PHONY来伪造一下。总结就是:.PHONY后面的target表示的是一个伪造的target, 而不是真实存在的target文件。 .PHONY后面的target也是此Makefile所支持的命令。默认只执行make命令时,实际就想当于执行make all.
make则执行“all:”下面的内容,直到出现另外一个标识。
make clean则执行“clean:”下面的内容,直到出现另外一个标识。
注意:标识内容下面的空白(例如@echo $^之前的空白)均是tab,不是空格。
2. 依赖关系
all: $(TARGET) 或者 $(TARGET):$(OBJS) 这种,由冒号隔开,即存在依赖关系,表示前面的标识依赖后面的内容。其中$(TARGET) 表示取出TARGET中的值(即main),故all: $(TARGET)可以翻译为all: main。详细的可以参考下面链接中的说明:makefile文件中的依赖关系理解_墨墨无文的博客-CSDN博客_makefile 依赖
3. 编译
@$(CC) $(CFLAGS) -o $@ $^ -Wl,--start-group $(MPI_LIBS) -Wl,--end-group,-gc-sections,-g
$(xxx)是取xxx中的值,用前面的内容,替换其中的值,就可以得到正常的编译命令。
4. 参数说明
1)@
放在整行之前,表示不打印此行内容。举例:@echo "start the compilexxxxxxxxx",实际打印出来的内容为“start the compilexxxxxxxxx”, 如果没有此@,则打印出两行,分别是:
echo "start the compilexxxxxxxxx"
start the compilexxxxxxxxx
2)-o
生成指定的输出文件。用在生成可执行文件时。默认的时候, gcc 编译出来的文件是 a.out,如果不想使用这个名字,则可以使用此关键字,后面跟你想要的可执行文件的名字即可。本事例使用的名字是-o $@
3)$@
表示目标文件。在此文中,$@的内容即是$(TARGET),即是main。所以-o $@即是-o main.
4) $^
表示所有的依赖文件,以空格分隔。如果依赖文件中有重复,那么这个变量会去除重复文件,只保留一份。在此文中,$^的内容即是$(OBJS),即是$(SMP_SRCS:%.c=%.o),即是test_resize.cpp。更多符号意思可以参考下面的链接:Makefile中的一些符号介绍_guanghma的博客-CSDN博客_makefile 符号
5)--start-group and --end-group
如果有多个静态库文件需要一起编译,那可以使用这两个参数将库包含起来。并且静态库要放在源码的后面,gcc是对文件的放置顺序有要求的。切记切记。此文中源码$^ 放在了库文件$(MPI_LIBS)之前。大致原因是gcc先编译的文件,如果其中没有找到对应的函数,则将这些函数暂存为未解析的符号,然后再编译后面的文件是,从后面的文件中找此未解析符号对应的源码,如果有,则将未解析的符号进行删除。此过程只顺序进行一次,如果反过来,则未解析的符号就找不到对应的源码了,对导致编译错误。相信解释可以参考下面的链接:为什么gcc中'-l'选项的顺序很重要? [重复]_编程黑洞网
--start-group ... --end-group:之间的内容只能为文件名或-l选项;为了保证内容项中的符号能被解析,链接器会在所有的内容项中循环查找。这种用法存在性能开销,最好是当有两个或两个以上内容项之间存在有循环引用时才使用。
6)-l
指定静态库的名称(例如-lgcc、 -lgcc_eh 、-lc实际是指文件名为libgcc.a、libgcc_eh.a、libc.a的库),此文中用到了-ldl -lm -lpthread -lrt。
7)-g
告知编译器,在编译的时候,产生调试信息
8)-L
链接外部静态库与动态库的查找路径。编译器会自动解析后面的一个参数。此文中的语句LDFLAGS = -L../libopencv_calib3d.a实际可以拆分为2行即 LDFLAGS = -L. 和 LDFLAGS += ./libopencv_calib3d.a。
9)-std=c++11
使用C++11的标准。
10)-O2
优化级别。分为-O0 、-O1 、-O2 、-O3。编译器的优化选项的 4 个级别,-O0 表示没有优化, -O1 为默认值,-O3 优化级别最高
11)-I
头文件所在的路径。此文中使用多个-Ixxx,表示有多个头文件的路径。
12)echo
打印命令,可以作为调试时打印一些参数的值。
13)SMP_SRCS:%.c=%.o
将SMP_SRCS中所有的.c替换为.o
参考链接:
GCC编译器30分钟入门教程
GCC 参数详解 | 菜鸟教程
gcc 命令,Linux gcc 命令详解:基于C/C++的编译器 - Linux 命令搜索引擎
程序的编译链接过程 - 可可西 - 博客园
OpenCV : undefined reference to cv::imread()_Sunshine_in_Moon的博客-CSDN博客