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。
参考链接:俊华的博客:GCC 编译详解及liuchao1986105的博客:gcc编译选项
这里着重讲一下链接。
函数库一般静态库(.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的使用方法。
对于简单的、文件比较少的工程,直接编写makefile,逻辑清晰可控。
GNU make的官方使用说明:http://www.gnu.org/software/make/manual/make.html
INCLUDE = -I $(OPENCV_ROOT)/include
其中, -I表示将$(OPENCV_ROOT)/include作为第一个寻找头文件的目录,如果找不到,会搜索系统默认路径。
LIBSPATH= -L/usr/local/lib -lopencv_imgcodecs
其中,-L表示将/usr/local/lib设为第一个寻找库文件的目录;-lopencv_imgcodecs表示在该路径中寻找libopencv_imgcodecs.so动态库文件。
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首先需要编写Cmakelist.txt,随后运行如下命令生成makefile:
mkdir build
cd build
cmake ..
接下来利用生成的makefile进行编译,即可生成可执行文件:
make -j12 VERBOSE=1 #VERBOSE=1表示打印出编译的详情
该方法为外部构建,即中间文件和可执行文件都放在build目录中,与source_dir不同,从而保持代码目录的整洁。
下面讲解cmakelist的编写:
对于单独的cpp文件,无依赖lib,且无.h或.hpp头文件的情况,可以直接编译源文件,生成可执行文件
ADD_EXECUTABLE(main hello.cpp)
对于稍复杂工程,需要添加头文件和链接lib。
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_LIST_DIR}/include)
编译器到指定的INCLUDE_DIRECTORIES路径下寻找cpp文件中include的.h和.hpp头文件。注意:不会自动遍历该路径的子目录,如果头文件在其子目录中,需要在.cpp代码中指定目录,例如: #include "opencv2/opencv.hpp"
之所以说引入可执行文件,而不说但是编译源文件、生成可执行文件,是因为Cmakelist语法是先引入可执行文件名称以及构建该可执行文件的源文件,随后链接库文件才能真正生成可执行文件。
aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src SRCLIST)
ADD_EXECUTABLE(main ${SRCLIST})
(cmake的指令是大小写无关的,add_executable和ADD_EXECUTABLE意义相同)
下面举例给出了链接库文件的几种使用方式。
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
)
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等。
对于更复杂的工程,可能需要通过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)
project (projectname)
指定项目名,同时cmake隐式地定义了两个变量,
同时,隐式定义了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
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是linux系统下的调试器,是很多IDE的内核。直接使用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模式下程序崩溃无法找到对应的源码中的行号等与源码关联的信息,只有二进制代码的位置等信息。
进入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
在很多程序崩溃时(非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