C/C 使用makefile / cmake 构建工程

Table of Contents

一、基础知识

1.1 编译器:

1.2 编译四步:

1.3 链接:

二、MAKE

2.1 指定头文件路径

2.2 指定链接库

2.3 编译源文件

三、CMAKE

3.1 编译源文件

3.2 加入头文件 & 链接库文件

a) 加入头文件

b) 引入可执行文件

c) 链接库文件

PS: find_package()

3.3 生成库文件

3.4 其他常用命令

四、gdb调试

 


为了编译C++或C语言的程序,可以使用cmake工具生成makefile,然后make编译;也可以直接编写makefile后进行make。

一、基础知识

1.1 编译器:

  • gcc: gnu的C编译器;
  • g++:gnu的C++编译器。

1.2 编译四步:

  1. 预处理Pre-processing:把头文件写入cpp,生成.i的文档;   (预处理器cpp)
  2. 编译Compiling,检查语法错误,把代码翻译成汇编语言,生成文档.s; (编译器egcs) 
  3. 汇编Assembling,把编译生成的.s文件转为目标文件,生成.o的文档,即二进制的机器代码;(汇编器as) 
  4. 链接Linkling,将每个cpp文件生成的.o文件以及函数库链接在一起,生成可执行文件 。(链接器ld,即linker eDitor)

参考链接:俊华的博客:GCC 编译详解及liuchao1986105的博客:gcc编译选项

1.3 链接:

这里着重讲一下链接。

函数库一般静态库(.a文件)和动态库(.so文件)

  • 静态库在编译链接时,把库文件的代码全部加入可执行文件中,运行时无需库文件,但生成的文件比较大;
  • 动态库在编译链接时不把库文件的代码加入到可执行文件中,而在程序执行时由运行时的链接文件加载库,以降低文件大小。

gcc在编译时默认使用动态库。

链接动态库有四种方法:

a) 动态库添加到/lib或者/usr/lib文件夹中,系统会默认搜索这些路径;

b) 每次运行程序前,临时将动态库所在的路径添加到环境变量LD_LIBRARY_PATH中,例如:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:TensorRT-5.1.5.0/lib

    此时,如果打开一个新的终端,则在之前的终端中添加的LD_LIBRARY_PATH无效。

c) 在配置文件中bashrc, /etc/profile或者/etc/ld.so.conf中添加动态链接库路径,例如:

sudo gedit ~/.bashrc

  在文件末尾加入

export LD_LIBRARY_PATH=/home/yly/Software/TensorRT-5.1.5.0/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

 可以通过如下命令查看LD_LIBRARY_PATH:

echo $LD_LIBRARY_PATH

d) 在Cmake或makefile中添加libspath。

参考链接: 阿进的写字台的博客:运行时动态库

接下来分别介绍cmake和makefile的使用方法。

二、MAKE

对于简单的、文件比较少的工程,直接编写makefile,逻辑清晰可控。

GNU make的官方使用说明:http://www.gnu.org/software/make/manual/make.html

2.1 指定头文件路径

INCLUDE = -I $(OPENCV_ROOT)/include 

其中, -I表示将$(OPENCV_ROOT)/include作为第一个寻找头文件的目录,如果找不到,会搜索系统默认路径。

2.2 指定链接库

LIBSPATH= -L/usr/local/lib -lopencv_imgcodecs 

其中,-L表示将/usr/local/lib设为第一个寻找库文件的目录;-lopencv_imgcodecs表示在该路径中寻找libopencv_imgcodecs.so动态库文件。

2.3 编译源文件

2.3.1 语法规则

target … : prerequisites …
        recipe
        …

Makefile中清晰地指明了生成的目标文件名(target),目标文件的所有依赖文件(prerequisites),和生成规则(recipe)。

注意:recipe前要为

2.3.2 使用cpp生成 .o 文件,例如:

target.o:source.cpp
    g++ $(INCLUDE) -c source.cpp  -o target.o

使用g++编译器;

recipe中的$(INCLUDE)指定的是搜索cpp中#include包含的头文件的路径;

-o后指定生成的文件

-c 对指定的cpp文件进行编译和汇编(但不链接)

注意:

prerequistites中可不添加头文件,这样的问题是如果修改了头文件,Makefile不会识别到修改,因此不会重新编译。

如果想在prerequisites中加入头文件,例如:target.o:source.cpp header.h,  Makefile并不支持自动在$(INCLUDE)路径下搜索prerequisites添加的头文件,因此,需要在prerequisites中给出header.h的路径(如果只写header.h,则只查找Makefile当前路径;如果header.h不在Makefile的同一路径下,则显示 No rule to make target ‘header.h’, needed by 'target.o'. Stop.

2.3.3 链接生成可执行文件

target: target.o target2.o
    $(CXX) -o $@ $^ $(LIBSPATH)

PS: recipe中常用通配符:

$@  表示目标文件
$^  表示所有的依赖文件
$<  表示第一个依赖文件
$?  表示比目标还要新的依赖文件列表

 

三、CMAKE

Cmake首先需要编写Cmakelist.txt,随后运行如下命令生成makefile:

mkdir build
cd build
cmake ..

接下来利用生成的makefile进行编译,即可生成可执行文件:

make -j12 VERBOSE=1  #VERBOSE=1表示打印出编译的详情

该方法为外部构建,即中间文件和可执行文件都放在build目录中,与source_dir不同,从而保持代码目录的整洁。

下面讲解cmakelist的编写:

3.1 编译源文件

对于单独的cpp文件,无依赖lib,且无.h或.hpp头文件的情况,可以直接编译源文件,生成可执行文件

ADD_EXECUTABLE(main  hello.cpp)

3.2 加入头文件 & 链接库文件

对于稍复杂工程,需要添加头文件和链接lib。

a) 加入头文件

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_LIST_DIR}/include)

编译器到指定的INCLUDE_DIRECTORIES路径下寻找cpp文件中include的.h和.hpp头文件。注意:不会自动遍历该路径的子目录,如果头文件在其子目录中,需要在.cpp代码中指定目录,例如: #include "opencv2/opencv.hpp"

b) 引入可执行文件

之所以说引入可执行文件,而不说但是编译源文件、生成可执行文件,是因为Cmakelist语法是先引入可执行文件名称以及构建该可执行文件的源文件,随后链接库文件才能真正生成可执行文件。

aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src SRCLIST)
ADD_EXECUTABLE(main ${SRCLIST})

(cmake的指令是大小写无关的,add_executable和ADD_EXECUTABLE意义相同)

c) 链接库文件

下面举例给出了链接库文件的几种使用方式。

LINK_DIRECTORIES(
    /home/Software/TensorRT-5.1.5.0/lib
    )
find_package(OpenCV 3.4 REQUIRED)
target_link_libraries(main
    -lnvinfer
     ${OpenCV_LIBS}  
    /usr/lib/x86_64-linux-gnu/libalglib.so  
    )
  • 通过LINK_DIRECTORIES(/home/Software/TensorRT-5.1.5.0/lib)指定目标库的路径,然后,在target_link_libraries(main -lnvinfer)中直接指定库的名称,gcc在编译时默认使用动态库;
  • 直接在target_link_libraries(main    /usr/lib/x86_64-linux-gnu/libalglib.so)中给出库的绝对路径;
  • 通过find_package(OpenCV 3.4 REQUIRED)直接找到对应模块的绝对路径,其中,find_package( [version] [REQUIRED]), [version]指定寻找的版本(可选),[REQUIRED]表示若未找到模块则停止。

PS: find_package()

find_package()有Module和Config两种模式。

Module模式:cmake --help-module-list 查看cmake可以添加的模块列表,或查看/usr/share/cmake-3.5/Modules下的.cmake文件。使用find_package()后,cmake自动给一些变量赋值,可通过cmake --help-module FindBoost查看变量。例如OpenCV_LIBS,cmake脚本中可以直接使用这些变量。

Config模式:如果在cmake的module下未查找到模块,则进入Config模式。Config模式查找顺序如下:

1、如果cmakelist中定义了_DIR(仅在该路径下查找,不查找其子目录)或_ROOT(在目录及其子目录查找),则在相应路径下查找Config.cmake或-config.cmake文件。例如:

OPENCV_ROOT=/home/yly/Software/opencv-3.4.6

2、在cmake特定的缓存变量或环境变量中查找。例如CMAKE_PREFIX_PATH.

添加方法一:在cmakelist中

set(CMAKE_PREFIX_PATH /home/yly/Software/opencv-3.4.6)

添加方法二:在编译时直接添加flag,

cmake -DCMAKE_PREFIX_PATH=/your/path ../

3、HINT字段制定路径。

4、系统环境变量PATH中(如果path以/bin/sbin结尾,则自动转为其父目录)

     例如,echo ${PATH}查看系统环境变量发现存在/usr/local/bin,则可以在/usr/local中查找,在其子目录找到OpenCV对应的.cmake文件,/usr/local/share/OpenCV/OpenCVConfig.cmake,该文件定义了find_package(OpenCV 3.4 REQUIRED)后系统所定义的变量OpenCV_LIBS、OpenCV_INCLUDE_DIRS等。

 

3.3 生成库文件

对于更复杂的工程,可能需要通过cpp文件生成库文件。

set(IMPORTER_SOURCES
    a.cpp
    b.cpp
    c.cpp)
add_library(abc SHARED ${IMPORTER_SOURCES})  # 动态链接库
target_include_directories(abc PUBLIC )
target_link_libraries(abc PUBLIC )

set(IMPORTER_SOURCES
    a.cpp
    b.cpp
    c.cpp)
add_library(abc_static STATIC ${IMPORTER_SOURCES})  # 静态链接库
target_include_directories(abc_static PUBLIC )
target_link_libraries(abc_static PUBLIC )

set(EXECUTABLE_SOURCES
    main.cpp)
add_executable(main ${EXECUTABLE_SOURCES})
target_include_directories(main PUBLIC )
target_link_libraries(main PUBLIC abc_static)

3.4 其他常用命令

project (projectname)

指定项目名,同时cmake隐式地定义了两个变量,_SOURCE_DIR和_BINARY_DIR。_SOURCE_DIR是存放cmakelist.txt路径,即cmake后面跟的路径参数;

_BINARY_DIR是cmake后生成的makefile文件存储的路径,注意:可能与生成的可执行文件的路径不同(如果通过set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)指定了可执行文件的存储路径的话)。

同时,隐式定义了PROJECT_SOURCE_DIR和PROJECT_BINARY_DIR,路径分别与_SOURCE_DIR和_BINARY_DIR一致,建议使用PROJECT_SOURCE_DIR和PROJECT_BINARY_DIR,不受工程名修改影响。

 

安装:

set(CMAKE_INSTALL_PREFIX /usr/local)

显示定义变量CMAKE_INSTALL_PREFIX,当使用make install时,将生成的可执行文件拷贝到指定的目录/usr/local/目录下。在cmakelist中显示定义CMAKE_INSTALL_PREFIX等价于在运行cmake时指定,即:

cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..

install(TARGETS main
                abc
                abc_static
        RUNTIME DESTINATION bin    #可执行文件
        LIBRARY DESTINATION lib    # 动态链接库  (.so文件)
        ARCHIVE DESTINATION lib)   # 静态链接库  (.a文件)

在DESTINATION定义的路径,如果使用"/"开头则表示绝对路径,CMAKE_INSTALL_PREFIX失效;如果不以"/"开头则表示相对路径,安装路径在${CMAKE_INSTALL_PREFIX}/DESTINATION定义的路径。

 

传递FLAGS给C++编译器:

set(CMAKE_CXX_STANDARD 11)

CMAKE_CXX_FLAGS定义的flag是编译每个.o文件时都会使用的。

CMAKE_EXE_LINKER_FLAGS

Linker flags to be used to create executables.仅在链接生成可执行文件时,CMAKE_EXE_LINKER_FLAGS指定的库会跟随在CMAKE_CXX_FLAGS指定的参数后。

set(CMAKE_EXE_LINKER_FLAGS "-lpthread")

在程序中使用多线程,需要使用pthread library,cpp文件中#include ,在链接时需要动态链接pthread库。

CHECK_CXX_COMPILER_FLAG( )

Check whether the CXX compiler supports a given flag. 例如:

CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)

如果编译器支持c++11则COMPILER_SUPPORTS_CXX11=1,否则为0。

打印消息

MESSAGE(STATUS "Project root directory:" ${PROJECT_SOURCE_DIR})

cmake隐式定义的变量

PROJECT_SOURCE_DIR:cmake 命令接的路径, 例如cmake .. 则PROJECT_SOURCE_DIR为当前路径的上一级目录。

CMAKE_CURRENT_SOURCE_DIR: CMakeLists.txt路径

 

PS: cmake缺点

跟已有体系接口不完善,例如有些程序下载后对应的是pkg-config,则通过find_package()无法找到对应的包。

 

四、gdb调试

gdb是linux系统下的调试器,是很多IDE的内核。直接使用gdb调试速度快,功能强大。可以查看程序崩溃位置和原因、设置断点调试等。

4.1 配置gdb调试模式

makelist中加入

set(CMAKE_BUILD_TYPE Debug)

或者

SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

 如未进行如上配置,则gdb模式下程序崩溃无法找到对应的源码中的行号等与源码关联的信息,只有二进制代码的位置等信息。

4.2 gdb断点调试命令:

进入gdb调试 gdb + 可执行文件名 ,例如gdb main
设定程序的输入参数 set args + 函数输入参数
显示程序行 l
显示程序中的某函数 l + 函数名
设置断点 b + 行号
开始运行(run) r
打印变量 p+变量名
继续运行,直到下一个断点 c
单步调试,如果遇到函数,则进入函数 s
单步调试,如果遇到函数,直接执行完函数 n
查看程序崩溃位置和原因 bt (即backtrace) 或 where
退出gdb调试 q

说明:表格中红色字体是常用的gdb调试命令,黑色字体表示设置断点调试相关命令。

参考:https://www.cnblogs.com/lsgxeva/p/8024867.html

 

4.3 gdb查看Coredump

在很多程序崩溃时(非gdb调试状态下运行),没有给出具体崩溃的位置和原因,但可以产生一个core dump文件。Coredump是进程在崩溃的那一刻生成的内存快照,通过gdb打开core文件可以分析定位程序崩溃的原因。

4.3.1 设置产生core文件

首先查看core file size,查看方法: 

ulimit -a

如果显示core file size为0,则程序不会生成core文件。为了产生core文件,修改方法:

ulimit  -c unlimited

PS: 以上设置仅在当前terminal内有效。

随后正常运行程序(不要gdb调试,gdb调试模式下崩溃不会产生core文件),如果程序崩溃,则会产生core文件,core文件默认存储在可执行文件的同一目录下。

4.3.2 产生core文件的信号

SIGABRT 

Abort signal from abort(3)

SIGBUS

Bus error (bad memory access)

SIGFPE

Floating-point exception

SIGILL

Illegal Instruction

SIGIOT 

IOT trap. A synonym for SIGABRT

SIGQUIT

 Quit from keyboard

 SIGSEGV

Invalid memory reference

SIGSYS

Bad system call (SVr4);

SIGTRAP

Trace/breakpoint trap

 SIGUNUSED

Synonymous with SIGSYS

SIGXCPU

CPU time limit exceeded (4.2BSD)

SIGXFSZ

 File size limit exceeded (4.2BSD)

参考:http://www.tin.org/bin/man.cgi?section=7&topic=signal

4.3.3 打开core文件方法:

gdb    可执行文件名    core文件名

 随后,使用bt或where命令可以定位到程序崩溃的位置。

https://blog.csdn.net/qq_39759656/article/details/82858101

 

你可能感兴趣的:(C/C 使用makefile / cmake 构建工程)