今天是新专栏《AI白身境》的第六篇,所谓白身,就是什么都不会,还没有进入角色。
对于大部分小白来说,因为python用的太爽,以致于或许都没有听说过CMake。python是脚本语言,而当前大量的AI算法都部署在移动端嵌入式平台,需要使用c/java语言,因此熟悉CMake和Makefile也是必备的基础。
作者 | 汤兴旺 言有三
编辑 | 汤兴旺 言有三
01
g++必备基础
在学习CMake和和Makefile之前我们先学下g++这个工具,大家或许会问为什么要学g++,不应该直接学CMake和Makefile吗。实际上如果你不掌握g++根本就不会写Makefile,因为它实际上就是对g++代码的整理,有了Makefile,执行程序会更加快速方便。另外CMake就是为了简化Makefile的编写,它可以自动生成Makefile。
1.1 安装g++
我们在安装g++之前可以看一下自己是否已经安装了g++,因为ubuntu安装后就默认安装了g++,下面命令可查看自己g++版本。
Tips:如果不想作死,就不要手贱去降级或者升级g++版本。
g++ --version
因为我已经安装了g++,出现了上面安装的版本号。如果你出现了上面信息,就不需要再安装了,没有的话,用下面的命令即可完成安装。
sudo apt-get install g++
安装好后也可以通过g++ --version查看是否安装成功
1.2 编译流程
现在我们已经安装好了g++,接下来通过写一个简单的程序来看看整个的编译流程。
我们通过vim创建一个test.cpp文件,测试的代码如下:
#include
using namespace std;
int main()
{
cout
return 0;
}
测试代码完成后,我们来进行下编译,打开终端,在终端输入g++ 文件名即可,在这个程序中就是下面命令:
g++ test.cpp
注意这里的文件名是包括路径的,要是不知道文件路径的话可以在敲完g++和空格之后直接把文件拖进去,系统会自动添加文件路径。
在终端完成上面的命令后,你发现并没有任何输出,但这时候你去主文件夹下(默认主文件夹)看下会发现有个a.out文件
现在你再在终端输入下面命令就能看到结果。
./a.out
接下来我来解释下这个.out文件,实际上这是个经过相应的链接产生的可执行文件。还有个.o文件,它是个中间文件,一般是通过编译的但还未链接。我们通过看看g++在执行编译工作的时候的流程,你就会有更好的理解。如下:
1.预处理,生成.i的文件
2.将预处理后的文件转换成汇编语言,生成.s文件
3.将汇编变为目标代码(机器代码),生成.o的文件
4.连接目标代码,生成可执行程序
对于这个流程,我们结合上面的例子,再详细介绍下,如下:
1.预处理阶段
首先在终端输入下面代码:
g++ -E test.cpp > test.i
预处理后的文件在 linux下以.i为后缀名,这个过程是用来激活预处理,执行完命令后,你会发现主文件夹下多了一个test.i文件
这一步(预处理)主要做了宏的替换,和注释的消除。
上图是test.i文件的最后部分,可以看见宏的替换和注释的消除。
2.将预处理后的文件转换成汇编语言
在终端输入下面代码:
g++ -S test.cpp
这一步主要就是生成test.s文件,.s文件表示汇编文件,用编辑器打开就都是汇编指令。下图是test.s文件的一部分。
3.将汇编语言变为目标代码(机器代码)
在终端输入下面代码:
g++ -c test.cpp
这一步就是生成目标文件,用编辑器打开就都是二进制机器码。
4.链接目标代码,生成可执行程序
在终端输入下面代码:
g++ test.o -o test
在这一步中生成的可执行程序名为test,如果执行命令 g++ test.o 这样默认生成a.out
最后我们再看下这个过程中产生的所有文件,如下:
这就是编译的整个过程,你掌握了吗,这个过程对于后面编写Makefile非常重要,一定要深刻理解。
02
Makefile必备基础
上面我们对g++和编译过程进行了介绍,现在我们继续学习如何编写Makefile。
2.1 Makefile介绍
Makefile描述了整个工程的编译、链接等规则,它定义了一系列规则来指定哪些文件需要编译以及如何编译、需要创建哪些库文件以及如何创建这些库文件、如何产生我们想要的可执行文件。
而且Makefile可以有效的减少大工程中需要编译和链接的文件,只编译和链接那些需要修改的文件,可以说使用Makefile,整个工程都可以完全自动化编译。
2.2 Makefile基本格式
target ... : prerequisites ...
command
...
...
target - 目标文件, 可以是 Object File, 也可以是可执行文件
prerequisites - 生成target所需要的文件或者目标
command - make需要执行的命令(任意的shell命令),Makefile中的命令必须以 [tab] 开头
2.3 Makefile语法
Makefile包含了五个重要的东西:显示规则、隐晦规则、变量定义、文件指示和注释。详细解释如下:
1. 显示规则:
通常在写makefile时使用的都是显式规则,这需要指明target和prerequisite文件。一条规则可以包含多个target,这意味着其中每个target的prerequisite都是相同的。当其中的一个target被修改后,整个规则中的其他target文件都会被重新编译或执行。
2. 隐晦规则:
make的自动推导功能所执行的规则
3. 变量的定义:
Makefile中定义的变量,一般是字符串
4. 文件指示:
Makefile中引用其他Makefile;指定Makefile中有效部分;定义一个多行命令
5. 注释:
Makefile只有行注释 "#", 如果要使用或者输出"#"字符, 需要进行转义, "\#
2.4 Makefile简单实例
尽管上面介绍了许多Makefile的知识点,但我相信一定你很晕,接下来我通过一个实例来说明如何编写Makefile。
2.4.1 准备程序文件
我们使用opencv对下面这只可爱的猫进行读取显示。
在这里我们用c++和opencv对图片进行读取和显示,程序保存在DisplayImage.cpp这个文件里,代码如下:
#include
#include
using namespace cv;
int main(int argc, char** argv )
{
if ( argc != 2 )
{
printf("usage: DisplayImage.out \n");
return -1;
}
Mat image;
image = imread( argv[1], 1 );
if ( !image.data )
{
printf("No image data \n");
return -1;
}
namedWindow("Display Image", WINDOW_AUTOSIZE );
imshow("Display Image", image);
waitKey(0);
return 0;
}
2.4.2 Makefile编写
上面我们已经准备好了.cpp文件,现在我们来编写Makefile进而进行编译,程序如下:
现在我来解释下应该如何编写这个Makefile,对于编写Makefile我建议从下往上写。步骤如下:
1.编写clean
这一步在Makefile中基本差不多,它的作用就是删除所有的.o文件和可执行文件。为什么这样做呢?我举个例子说明下,如果你有100个.cpp文件,经过编译后会得到一个可执行文件。在这个过程中我们会得到许多不必要的文件,例如100个.o文件,但这个文件又没有用,如果用rm的话那就太麻烦了,所以我们用了clean,它可以很轻松完成这个任务。另外请注意Makefile文件在执行时不会执行clean这个命令,需要我们调用才会执行,即make clean。clean代码如下:
2.编写目标文件1:依赖文件1
目标文件就是你想得到的文件,依赖文件就是你目前所拥有的东西。在本实例中我们现在拥有DisplayImage.cpp,所以DisplayImage.cpp是依赖文件,我们想得到DisplayImage.o,所以它是目标文件。代码如下:
3.编写目标文件2:依赖文件2
这一步的依赖文件2实际就是第二步的目标文件1,在第二步我们通过DisplayImage.cpp得到了DisplayImage.o,现在我们需要通过DisplayImage.o得到可执行文件DisplayImage。所以在这一步目标文件是DisplayImage,依赖文件是DisplayImage.o,代码如下:
4.应用opencv库和头文件
这一步就需要根据自己计算机来配置了,对于我们初学者来说挺麻烦的,可以自己尝试下。有问题可以联系我们。
编写完makefile后,我们在终端make下就行了。下面编译后的文件:
最后在终端输入下面代码即可显示图片。
./DisplayImage 01.jpg
总体来说编写Makefile可以按照这个套路写,多写几次就会了。
03
CMake必备基础
说完Makefile,我们再说下CMake。CMake是一个跨平台的编译(Build)工具,可以用简单的语句来描述所有平台的编译过程,其是在make基础上发展而来的,早期的make需要程序员写Makefile文件,进行编译,而现在CMake能够通过对cmakelists.txt的编辑,轻松实现对复杂工程的组织。下面我带大家学习下CMake的基础知识。
3.1 安装CMake
首先我们看看如何在自己的linux系统(我的系统Ubuntu18.04)下安装CMake。方法如下:
sudo apt-get install cmake
输入上面命令后实际上就安装成功了,可以通过下面命令来检查:
cmake --version
如果你的界面如下图所示即说明安装成功。
3.2 CMake编译流程
成功安装好CMake后我们再来说说如何在linux平台下使用CMake生成Makefile并编译的流程,如下:
1.编写CMake配置文件CMakeLists.txt,我们可以认为CMakeLists.txt就是CMake所处理的"代码"。
2.执行命令 cmake path生成Makefile,其中path是CMakeLists.txt所在的目录。
3.使用make命令进行编译。
3.3 使用CMake编译程序
我们通过一个关于opencv读取图片的程序,让大家更好的理解整个CMake的编译过程。
3.3.1 准备程序文件
这里程序准备可以按照第二部分makefile那里准备。最后文件目录结构如下:
├── build
├── CMakeLists.txt
├── DisplayImage.cpp
opencv读取图片的程序写完后,我们需要编写CMake处理的代码了,即CMakeLists.txt。
3.3.2 编写CMakeLists.txt
现在我们编写CMakeLists.txt文件,该文件实际上放在哪里都可以,只要编写的路径能够正确指向就好了,CMakeLists.txt文件内容如下所示:
cmake_minimum_required(VERSION 2.8)
project( DisplayImage )
find_package( OpenCV REQUIRED )
add_executable( DisplayImage DisplayImage.cpp )
target_link_libraries( DisplayImage $ )
看到这些代码是不是很闷逼,为了让大家明白CMakeLists.txt文件内容,接下来我说一下Cmake的一些常用命令,你就能很好的理解上面的代码了。
1)cmake_minimum_required命令
命令语法:cmake_minimum_required(VERSION major[.minor[.patch[.tweak]]][FATAL_ERROR])
命令简述:用于指定需要的CMake 的最低版本
使用范例:cmake_minimum_required(VERSION 2.8)
2)project 命令
命令语法:project(
[languageName1 languageName2 … ] )
命令简述:用于指定项目的名称,一般和项目的文件夹名称对应
使用范例:project(DisplayImage)
3)aux_source_directory命令
命令语法:aux_source_directory( )
命令简述:用于将 dir 目录下的所有源文件的名字保存在变量 variable 中
使用范例:aux_source_directory(src DIR_SRCS)
4)add_executable 命令
命令语法:add_executable( [WIN32] [MACOSX_BUNDLE][EXCLUDE_FROM_ALL] source1 source2 … sourceN)
命令简述:用于指定从一组源文件 source1 source2 … sourceN 编译出一个可执行文件且命名为name
使用范例:add_executable( DisplayImage DisplayImage.cpp )
5)target_link_libraries命令
命令语法:target_link_libraries( [item1 [item2 […]]][[debug|optimized|general] ] …)
命令简述:用于指定 target 需要的链接 item1 item2 …。这里 target 必须已经被创建,链接的 item 可以是已经存在的 target(依赖关系会自动添加)
使用范例:target_link_libraries( DisplayImage $ )
6)add_subdirectory 命令
命令语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
命令简述:用于添加一个需要进行构建的子目录
使用范例:add_subdirectory(Lib)
7)include_directories 命令
命令语法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)
命令简述:用于设定目录,这些设定的目录将被编译器用来查找 include 文件
使用范例:include_directories($/lib)
像这样的命令还有很多,如find_package()寻找使用第三方库等,这些都需要我们平时多加积累。给大家一个查询命令的方法,大家可以多去看cmake官网的help,链接如下:
https://cmake.org/cmake/help/v2.8.8/cmake.html#section_Commands
3.3 编译和运行程序
现在CMakeLists.txt文件已经编写好了,意味着我们的工作即将进入尾声。现在看看我们的文件结构目录,如下图:
接下来我们就需要进行编译了。编译的过程相对于CMakeLists.txt文件的编写是很简单的,只有两步,如下
cmake
make
其中cmake命令将CMakeLists.txt文件转化为make所需要的makefile文件,最后用make命令编译源码生成可执行程序或共享库。对于我们这个实例,编译如下:
首先我们在命令行输入cmake .(注意cmake和.之间有空格),表明Cmakelist.txt文件在当前目录下。
接下来在命令行输入make
这样我们就编译成功了,我们看下编译后的文件目录
解释下这个build文件夹,由于cmake后会生成很多编译的中间文件以及makefile文件,所以一般建议新建一个新的目录,专门用来编译,这就是这里的build,打开build后,里面的文件如下:
到这里,我们不禁要问怎么没有图片显示呢,别急,在build目录下的命令行输入下面命令即可显示图片,这就是生产的DisplayImage可执行文件。
./DisplayImage ../01.jpg
到这里,关于CMake的一些基本操作就介绍的差不多了,其实对于CMake的学习我认为必须在实例中多加应用,才能更好的掌握,因为它的复杂命令太多了。
总结
CMake和Makefile的基础我们就介绍完了,对于这两个工具其实不是一时就能学会的,需要大量的实践积累才能游刃有余。
下期预告:下一期我们会讲AI领域必须掌握的数据爬虫基础,如果你有建议,欢迎留言,我们会及时采纳的。
转载文章请后台联系
侵权必究
更多请关注知乎专栏《有三AI学院》
想要成为变身”AI专家“,就戳戳手指关注我们吧
别忘了点“好看”支持作者噢
领取专属 10元无门槛券
私享最新 技术干货