c++ 编译链接, make, cmake 整理

经验总结,具体请自行根据实际情况决定。(细节持续补充中…)

  • 2016-10-25 补充了一个makefile模板
  • 2016-10-27 补充了cmake及cmake生成nmake,整理了windows下有关编译的目录,有助于理解vs的x86和x64编译过程。
  • 2017-6-7 加入了一个gcc选项,关于库的依赖顺序
  • 2019-5-28 加入了 -l: gcc选项
  • 2019-7-24 加了一个tips, make > rd.txt 2>&1
  • 2020-10-28 加入了调试时需要的命令
  • 2020-12-12 补充 glibc 相关

前言

无论在windows上还是linux上我们都遇到很多编译链接的问题,如果对这些了解不透彻,那么makefile这些都是写不好的,即使用vs这样的软件,如果你没有清晰的思路盲目修改,那永远走不出泥潭。
至于说有关makefile的工具,windows下有nmake,linux下有make,还有一些稍有封装的工具cmake,qmake。无论是哪种只要明白了编译链接的目的(不是原理啊)才能很好地写出来。

要注意把编译和链接两个过程区分开,不要混为一谈

编译

编译中和我们相关的主要过程其实就是

  • 检查是否符合c++语法(c++11等)
  • 处理一些宏定义(需要用 -D 指出)
  • 看变量函数有没有定义,其实同第一点,如果你额外引入一些库就用 -I 参数指明它头文件的位置这是编译阶段最有可能出错的地方

这些错误是编译阶段的错误,如果你make的时候发现有这些错误,一定不要想什么缺库文件。不过一般编译阶段的错误非常容易解决这里就不多说了。

编译完之后一般对每个.cpp 生成一个.o

提醒
编译阶段没有错误,并不代表你程序写的没有问题。如果你对链接过程不熟悉,很可能写出来函数或变量重定义的错误。比如说你把一个函数func实现写在一个.h中,结果在两个.cpp中都引用了这个文件,结果生成两个.o,而这两个.o文件都有func的实现(假设在同一个作用域中),一旦他们同时编入到第三个cpp生成的.o中就会导致冲突。
不仅仅是函数,非静态或者非常量变量也是一样的道理。所以一般.h中定义static的 extern变量(然后在某个cpp中正式定义),让其他cpp用
【这个问题非常严重,而且你不懂这个原理很难解决】

经验

有时候自己写程序,本来已经有了头文件但是还是说某个函数或变量找不到,这时可能是没有使用using的原因。

链接

这个是重头戏,搞makefile的人,尤其是在linux对这个过程吃尽了苦头。
编译的过程主要检查的是你用一个函数有没有对其进行声明,而链接的过程则是你声明了,有没有给出其实现的方法(即使你没有实现,你给的.so .a 里面也要有实现) 如果没有找到就会报undefined reference的错误,表明找不到函数的定义(注意不是声明,如果是声明的话就是编译时候的问题了)

需要注意的事项

  1. 指定一个静态库一般使用静态库绝对路径 如 /path/libxxx.a ,动态库一般就 -L /path -lxxx,或者使用 (-Wl,-Bstatic -lxxx 链接静态, -Wl,-Bdynamic 动态 )
  2. 动态库的指定可能有一定的顺序, 一般来说最好先是自己的库,然后是三方库,然后是系统库。有时候链接不上但是位置正确,可能会是顺序问题(我觉得深层原因还是库名冲突),最佳的顺序是g++ ... obj($?) -l(上层逻辑lib) -l(中间封装lib) -l(基础lib) -l(系统lib) -o $@
  3. 如果你把库路径写在了环境变量LD_LIBRARY_PATH中,格式最好是 export LD_LIBRARY_PATH=your_lib_path:$LD_LIBRARY_PATH,就是你自己的库路径优先。这个还有一个好处是,如果你编译成功,但是运行的时候提示某个.so文件 not found, 这样可以解决,否则你在编译的时候用 运行时查找 的编译选项 -Wl,-rpath,your_lib_path

提醒
链接时候的两大问题,一个是链接不到库,一个是链接不到你想要的库(譬如你不想用系统自带的某个库之类的),其实解决方法是一样的。【要记住上面三点,自行选择】

运行

一般编译链接成功就能运行,运行时说缺少某些库那就参照 链接->注意事项的第三点的黑体部分,一般都能解决。

编译完成之后其实自己可以用 ldd 命令看看可执行程序及.so文件看看有没有not found的,即使没有not found 也看看对不对。

如果你编译生成的是静态库或动态库,你还可以用 readelf -d 大致看看是不是你想要的(你想编的有没有编进去)。

g++ 一般编译选项介绍

这个还是搜搜其他博客吧,我这里列出的比较少,但是是我为认为比较重要
-Wl 后面跟着的参数是传递给链接器ld的
-Wl,--as-needed 忽略链接时没有用到的动态库
-g -O0 就是生成可调试的程序(且不让编译器优化)之后用 gdb -tui 调试即可
--start-group和--end-group 放在这两个地方的库会自动解决依赖关系详见——再议GCC编译时的静态库依赖顺序问题
-l: -lxxx 一般是需要默认名字为 libxxx.so,如果你的名字不是这样命名的,就 -l:xxx.so.x
-Wno-overloaded-virtual 这个当时使用opencv时,有个编译器强制加上了 -Woverloaded-virtual 导致编译出错,然后我又加了这个。
-Wl,--allow-shlib-undefined 这个指令对于跨机器编译非常有用,当然有时候会导致运行失败,却很难定位出错误。

生成静态库时
一般 ar -cvq -o your.a *.o 就行。【具体查看别的博客吧】
我觉得静态库就是若干个.o文件的组合吧,可用 ar -t 查看一下

生成静态库
需要使用 -shared -fPIC 其实这是两个选项,后面那个属于编译阶段的,前面是链接阶段,指明生成链接库。

编译源码的流程

一般是三个步骤
最好先mkdir build, cd build,然后进行主要工作

  1. ../configure : 这个就是一个 bash 脚本,它主要负责生成makefile【简单理解makefile也就是为了生成长长的g++命令选项 】 一般这个文件是很规范的,比如可以用 --enable-static=yes 指明生成静态库,最重要的一个是--prefix=your_install_path,这个就是说你把软件装在哪里(在第三点细讲)
  2. make 这个就是执行上面生成的make,一般为了快速编译用 make -j / make -j4。生成的可执行程序,库文件都在当前的目录下面。
  3. make install 这一步就是安装,第二步生成的东西最后要装在系统的哪个位置呢?如果你不指定一般是在/usr 或 /usr/local 下面(这时大部分就需要用sudo执行了了),如果指定就是第一步中的prefix参数,这样安装的时候就不用root权限了。如果安装在自己的目录中,自行根据使用情况配置环境变量 (当然也可以不安装,毕竟需要的文件在第二步就已经有了)

安装源码一般就是使用其库文件之类的,自己编译的时候就指定你安装的目录的头文件和库文件。
安装好后,删掉build就行。此外 make uninstall 就是卸载了。

glibc

[尝试撰写中……]
链接的时候很多情况下会落到 GLIBC 版本不对。因为很多库最终会用到 libc.so、libm.so。
然而这个库可不是普通的库(并不像 stdc++一样是由编译器决定的),它基本是操作系统自带的,操作系统很多基础工具(命令)都是基于它,它的改变很有可能导致系统崩溃,而且安装也依赖很多库。于是关于 glibc 会衍生出如何编译glibc,以及出错后如何补救。

安装

Makefile

作用:首先不要把makefile看的太神秘,它主要就是方便你编译,你完全可以自己用脚本写。一定要认识到你的目的是为了调用编辑器(如g++)编译你的程序,写makefile也是这个目的,它就是最终帮你生成了g++这个软件所需要参数,你的编译还是g++啊,编译的报错除了你程序的问题就是传给的g++参数不对啊,而这个参数不对又是因为你makefile写的不对导致的

跳出makefile的外表后,你就知道一般编译个小项目没必要写makefile了,如果是自己写的复杂的项目可以使用cmake。

tips:
make的时候经常会报很多错误,如何记录下来.
make > rd.txt 2>&1 就是先把1(stdout)定向到文件中,然后把2(stderr)定向到1, 这样就行了

一个makefile例子,可作为模板

CXX=g++

CXXFLAGS = -I/home/chj/face/program/include -I/home/chj/face/install/opencv2/include -I/home/chj/face/program/3rdparty
LDFLAGS = -L/home/chj/face/install/opencv2/lib -lopencv_core -lopencv_highgui -lopencv_features2d
STATICLIB = /home/chj/face/program/build/libliblinear.a /home/chj/face/program/build/liblib3000fps.a   

# 便于直接make
all:main

TARGET = main

# 寻找所有需要的 cpp
CHJ_SRCS := $(wildcard ../src/*.cpp) $(wildcard ../src/lbf/*.cpp)
# 将上面找到的 .cpp 换成 .o
CHJ_OBJS = $(patsubst %cpp, %o, $(CHJ_SRCS)) 

# $@ 为冒号之前的; $< 为冒号后第一个; $^ 为冒号后所有的
# 编译
%.o:%.cpp  
	# $(warning $@) 
	$(CXX) $(CXXFLAGS) -c $< -o $@ 

# 链接 (我这里某个文件有int main(),就直接生成可执行程序)   
$(TARGET):$(CHJ_OBJS)
	# 顺序很重要, $(STATICLIB) 放在前面还不行
	$(CXX) $(LDFLAGS) -o $@ $^ $(STATICLIB) 

cmake

cmake等我用在windows上的时候才觉得他真的方便。好多库都提供cmakelist然后直接用win下的cmake程序生成vs的项目,再用vs打开就行。

常用的语法

  • cmake_minimum_required(VERSION 3.5) 这个一般必须要确定你的cmake最低版本
  • project(XXX) 给你的项目确定个名字,可能被用作默认的生成名XXX.exe
  • SET(CMAKE_CXX_FLAGS_RELEASE "${ENV{CXXFLAGS} -O3 -Wall") 这个set命令就是赋值的意思,所附的值可能是系统变量也可以是自己的变量。

一些常用的变量如下

下面这些变量一般都有默认值
CMAKE_C_COMPILER CMAKE_CXX_COMPILER CMAKE_CXX_FLAGS_DEBUG
CXXFLAGS 这个就是编译时候的选项
CMAKE_SOURCE_DIR 这个是你cmakelist.txt的目录

  • include_directories() 这个是添加头文件的目录,一个可以写多个目录
  • link_directories 添加库文件的目录
  • add_executable(face_align ${SOURCE_FILES}) 这就是你添加的生成的程序,后面是.cpp文件
  • target_link_libraries(face_align ${OpenCV_LIBS} dlib.lib ) 添加生成你的程序额外需要的库,如果只写库的名字,那么库的目录就必须在 link_directories 中给出

命令行中的语法

举个例子
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=release ..
生成nmake的makefile同时使用Release模式

指定find 的位置,但是如何添加多个呢?

cmake -D CMAKE_PREFIX_PATH="chj/bin/lib/cmake/opencv4/" ..

例子:编译库

假设你的.cpp在同一个目录下

FILE(GLOB hdrs_base "*.h" )
FILE(GLOB srcs_base "*.cpp")
FILE(GLOB hdrs ${hdrs_base}  )
FILE(GLOB srcs  ${srcs_base} )

ADD_LIBRARY(${PROJECT_NAME} ${srcs} ${hdrs})  # 应该是通过加 SHARED or STATIC 来决定生成什么库

调试手段

undefined symbol问题的查找、定位与解决方法

ldd: 查看是否有 not found 想法找到
ldd -r: 没找到的 symbol
nm -u / strings 查看是否真的没有,这种问题一般很少会真的没有。
c++filt <symbol>: 看看是哪些地方没找到 symbol
file xx.so: 看看与主机平台是否一致

下面进入有关windows下的链接部分。

win7下有关编译目录的整理

windows下和编译连接相关的几个文件为编译器cl.exe 链接器link.exe 资源编译器rc.exe 这里有较全的介绍

cl 与 link一般在C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\[amd64|...]
rc一般在C:\Program Files (x86)\Windows Kits\8.1\bin\[x64]
注意:上面加中括号表示那时x64位程序的目录所以如果使用cmake生成nmake文件时一定要注意选好合适的编译器。

nmake (win下非IDE编译)

不想细讲语法了,至今没有直接用过,一般直接借助win下的cmake然后用vs了,或者用cmake生成nmake需要的文件,或者装上mingw然后直接make。

如果要用nmake就需要指定其目录,可以用相应的vs版本的nmake。比如说如果要用vs2015 64位编译就先在命令行中运行
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
如果写在batch脚本中就在前面加个 start

用visual studio编译时的配置及经验

一般你编写的项目总是需要依赖很多库的,比如说opencv,因此你的项目需要至少指定opencv 头文件所在的目录库文件所在的目录,每次这样指定很麻烦,不妨自己写个.props然后在属性管理器中加入。
下面是我的一个模板,自行修改相应的位置(目录最好用绝对路径)ExecutablePath这个可以不要。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ImportGroup Label="PropertySheets" />
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup>
    <ExecutablePath>opencv\3.1.0\opencv\build\x64\vc12\bin;$(ExecutablePath)</ExecutablePath>
  </PropertyGroup>
  <PropertyGroup>
    <IncludePath>opencv\3.1.0\opencv\build\include;$(IncludePath)</IncludePath>
  </PropertyGroup>
  <PropertyGroup>
    <LibraryPath>opencv\3.1.0\opencv\build\x64\vc12\lib;$(LibraryPath)</LibraryPath>
    <LibraryWPath>$(LibraryWPath)</LibraryWPath>
  </PropertyGroup>
  <ItemDefinitionGroup>
    <Link>
      <AdditionalDependencies>opencv_world310d.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
  </ItemDefinitionGroup>
  <ItemGroup />
</Project>
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值