C++编译的基本知识
1 C++编译的基本知识
对于源代码包,我们只有编译才能在系统上运行。
在一个 C++ 工程中,并不是所有代码都会编译成可执行文件。只有带有 main 函数的文件才会生成可执行程序。而另一些代码,我们只想把它们打包成一个东西,供其他程序调用。这个东西叫做库。 一个库往往是许多算法、程序的集合,我们会在之后的练习中,接触到许多库。例如,OpenCV 库提供了许多计算机视觉相关的算法,而 Eigen 库提供了矩阵代数的计算。 Linux下的编译器有gcc、g++,随着源文件的增加,直接用gcc/g++命令的方式显得效率低下,人们开始用Makefile来进行编译。然而随着工程体量的增大,Makefile也不能满足需求,于是便出现了Cmake工具。CMake是对make工具的生成器,是更高层的工具,它简化了编译构建过程,能够管理大型项目,具有良好的扩展性。对于ROS这样大体量的平台来说,就采用的是CMake,并且ROS对CMake进行了扩展,于是便有了Catkin编译系统。
2 使用g++
我们用编译器 g++ (g++ 是一个 C++ 编译器)把.cpp
文件编译成一个可执行文件 。
g++ helloSLAM.cpp
刚才这条编译命令把 helloSLAM.cpp 这个文本文件编译成了一个可执行程序。我们检查当前目录,会发现多了一个 a.out 文件,而且它具有执行权限(终端里颜色不同)。我们输入./a.out 即可运行此程序
3 使用 cmake
3.1 cmake简介
单个C++文件可以用g++来编译成一个可执行文件。但是一个大工程使用 cmake 来管理源代码 。
理论上说,任意一个 C++ 程序都可以用 g++ 来编译。但当程序规模越来越大时,一个工程可能有许多个文件夹和里边的源文件,这时输入的编译命令将越来越长。通常一个小型 c++ 项目含有十几个类,各类间还存在着复杂的依赖关系。 可能原文件中其中一部分要编译成可执行文件,另一部分编译成库文件。 如果仅靠 g++ 命令,我们需要输入大量的编译指令,整个编译过程会变得异常繁琐。
3.2 cmake使用介绍,与生成可执行文件
在一个 cmake 工程中,我们会用 cmake 命令生成一个 makefle 文件,然后,用 make命令 ,根据这个 makefle 文件的内容,编译整个工程。 cmake 过程处理了工程文件之间的关系,而 make 过程实际调用了 g++ 来编译程序 。
cmake的操作对象实际上是CMakeLists.txt 文件,因此需要开发者先编写一个CMakeLists.txt 文件CMakeLists.txt 文件告诉 cmake 我们要对这个目录下的文件做什么事情。CMakeLists.txt 文件内容需要遵守 cmake 的语法。
比如:
# 声明要求的 cmake 最低版本
cmake_minimum_required( VERSION 2.8 )
# 声明一个 cmake 工程
project( HelloSLAM )
# 添加一个可执行程序
# 语法: add_executable( 自定义程序名 源代码文件 )
add_executable( helloSLAM helloSLAM.cpp )
演示了最基本的工程:指定一个工程名和一个可执行程序(这也是如何使用cmake生成可执行文件的示例)。
过程:
在当前目录下 ,首先用cmake对该工程进行分析,再用make指令对工程进行编译。
第一步
cmake .
注意加这个.
说明要编译的源文件以及CMakeLists.txt 文件应当都在打当前目录下。
第二步:
make
cmake 会输出一些编译器等信息,然后在当前目录下生成一些中间文件,其中最重要的就是 MakeFile。MakeFile 是一个自动化编译的脚本,可以将它理解成一系统自动生成的编译指令。
效果:
cmake 过程处理了工程文件之间的关系,而 make 过程实际调用了 g++ 来编译程序。虽然这个过程中多了调用cmake 和 make 的步骤,但我们对项目的编译管理工作,从输入一串 g++ 命令,变成了维护若干个比较直观的 CMakeLists.txt 文件,这将明显降低维护整个工程的难度。比如,当我想新增一个可执行文件时,只需在 CMakeLists.txt 中添加一行“add_executable”命令即可,而后续的步骤都是不变的。 cmake 会帮我们解决代码的依赖关系,无需我们输入一大串 g++ 命令。
删除中间文件:
现在这个过程中,唯一让我们不满的是, cmake 生成的中间文件还留在我们代码文件当中。当我们想要发布代码时,并不希望把这些中间文件一同发布出去。这时我们还需把它们一个个删除,这十分的不便。一种更好的做法是让这些中间文件都放在一个中间目录中,在编译成功后,把这个中间目录删除即可。所以,更常见的编译 cmake 工程的做法就是这样 :
mkdir build
cd build
cmake ..
make
我们新建了一个中间文件夹“build”,然后进入 build 文件夹,通过 cmake .. 命令,对上一层文件夹,也就是代码所在的文件夹进行编译。这样, cmake 产生的中间文件就会生成在 build 文件夹中,与源代码分开。当我们发布源代码时,只要把 build 文件夹删掉即可。 下次编译的时候再像这样新建。
3.3 cmake生成库文件
只有带有 main 函数的文件才会生成可执行程序。而另一些代码,我们只想把它们打包成一个东西,供其他程序调用。这个东西叫做库。
一个库往往是许多算法、程序的集合(由不含main函数的源文件编译而成),我们会在之后的练习中,接触到许多库。例如,OpenCV 库提供了许多计算机视觉相关的算法,而 Eigen 库提供了矩阵代数的计算。
3.3.1 使用cmake 生成库
比如对于一个libHelloSLAM.cpp 文件 。这个库提供了一个 printHello 函数,调用此函数将输出一个信息。但是它没有 main函数,这意味着这个库中没有可执行文件。
//这是一个库文件
#include <iostream>
using namespace std;
void printHello()
{
cout<<"Hello SLAM"<<endl;
}
这个库提供了一个 printHello 函数,调用此函数将输出一个信息。但是它没有 main函数,这意味着这个库中没有可执行文件。为了libHelloSLAM.cpp
编译成库文件,我们在 CMakeLists.txt 里加一句:
add_library( hello libHelloSLAM.cpp )
这条命令告诉 cmake,我想把这个文件编译成一个叫做“hello”的库。之后便可以使用 cmake 和make编译整个工程:
cd build
cmake ..
make
这时,在 build 文件夹中会生成一个 libhello.a 文件,这就是我们得到的库(静态库),以.a 作为后缀名。
在 Linux 中,库文件分成静态库和共享库两种。静态库以.a 作为后缀名,共享库以.so结尾。所有库都是一些函数打包后的集合,差别在于静态库每次被调用都会生成一个副本,而共享库则只有一个副本,更省空间。如果我们想生成共享库而不是静态库,只需用:
add_library( hello_shared SHARED libHelloSLAM.cpp )
就可以编译成一个共享库。此时得到的文件是 libhello_shared.so 了。
3.3.2 使用库文件
库文件是一个压缩包,里头带有编译好的二进制函数。不过,仅有.a 或.so 库文件的话,我们并不知道它里头的函数到底是什么,调用的形式又是什么样的。为了让别人(或者自己)使用这个库,我们需要提供一个头文件,说明这些库里都有些什么。因此,对于库的使用者, 只要拿到了头文件和库文件,就可以调用这个库了。
编写一个库文件对应的头文件:
libHelloSLAM.h
#ifndef LIBHELLOSLAM_H_
#define LIBHELLOSLAM_H_
void printHello();// 对库文件中的函数进行声明,以让别人知道库文件里有什么
#endif
这样,根据这个文件和我们刚才编译得到的库文件,就可以使用这个函数了。下面我
们写一个可执行程序,调用这个简单的函数void printHello()
:
useHello.cpp
#include "libHelloSLAM.h"
int main( int argc, char** argv )
{
printHello();
return 0;
}
然后,在 CMakeLists.txt 中添加一个可执行程序的生成命令,链接到刚才我们使用的库上:
add_executable( useHello useHello.cpp ) //编译刚编写的可执行文件
target_link_libraries( useHello hello_shared )//将共享库链接到某个头文件
通过这两句话, useHello 程序就能顺利使用 hello_shared 库中的代码了。
对于他人提供的库,我们也可用同样的方式对它们进行调用,整合到自己的程序中。
3.4 小结
- 首先,程序代码由头文件和源文件组成;
- 带有 main 函数的源文件编译成可执行程序,其他的编译成库文件。
- 如果可执行程序想调用库文件中的函数,它需要参考该库提供的头文件,以明白调用
的格式。同时,要把可执行程序链接到库文件上。 (但是根据描述是不需要编译头文件的)
使用g++或者cmake本质是使用文本开发环境,即使用文本编辑器编辑代码,再对编辑好的代码进行编译等生成文件。除此之外,还可以使用集成开发环境。