编译初探究之gcc&make&cmake
GCC介绍
GCC:是 GNU Compiler Collection(GNU 编译器套装)缩写,一个由 GNU 开发用于 Linux 系统下编程的编译器。GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。
gcc是GCC中的GUNC Compiler(C编译器)g++是GCC中的GUN C++ Compiler(C++编译器)
但是并 不是说 gcc 编译 c 语言,g++ 编译 c++。而是gcc调用了 C compiler,而 g++ 调用了 C++ compiler。主要区别在于: 
- 对于 
*.c和*.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的) - 对于 
*.c和*.cpp文件,g++则统一当做cpp文件编译 - 使用
g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL gcc在编译C文件时,可使用的预定义宏是比较少的gcc在编译cpp文件时 /g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏。- 在用 
gcc编译c++文件时,为了能够使用STL,需要加参数–lstdc++,但这并不代表gcc –lstdc++和g++等价,它们的区别不仅仅是这个。 
gcc
gcc编译单个文件
创建一个单个文件,如 vim hello.c :
1  |  | 
然后进行编译执行:
1  |  | 
gcc编译两个文件
需要创建两个源码文件,vim main.c,内容如下:
1  |  | 
然后创建第二个源码文件,vim seconds.c:
1  |  | 
执行编译过程:
1  |  | 
说明:上述过程中创建了两个源码文件,其中main.c文件中函数依赖于seconds.c文件中sin_value(double angle)函数。
首先是编译过程,通过gcc -c将源码文件编译成目标文件main.o和seconds.o。然后对目标文件进行链接操作,通过gcc -o将目标文件链接,并生成可执行文件binary。在链接过程中,由于seconds.c文件中调用了数学函数库math.h。需要依赖于外部的函数库。在Linux系统中,gcc默认会使用/lib和/lib64的函数库。如果要使用自定义位置的函数库,可通过-L{Path}来添加;当然可以通过-I{Path}来指定include文件的目录(这个默认使用的是/usr/include)。对于使用数学函数库math.h可以通过加-lm来指定,也就是表示要使用libm.so这个函数库。
make
其实对于上述gcc编译两个文件例子中,如果每次修改了源码,都需要重新执行编译和链接两个过程。如果一旦源码文件很多了,重复操作很繁琐。就可以通过make来一个步骤完成(使用makefile文件然后使用make宏编译)。在一些最小安装的Linux系统中,默认是不安装make的(Ubuntu安装make可通过:sudo apt-get install make)。
使用make编译小例子
在目录中创建一个makefile文件, vim makefile, 且内容如下(注意: 在每个 targets(main,clean) 下面一行都有个<tab>):
1  |  | 
然后需要通过cmake进行编译:
1  |  | 
注意,默认使用的文件名为makefile,当然可以在make时候通过添加参数-f指定文件名,比如Makefile文件编译可通过:make -f Makefile clean,并且如果makefile文件内容很多,导致运行过程中不知道有多少个targets,可以通过make <tab>来通过Bash补全。
makefile语法
makefile的基本语法规则为:
1  |  | 
在makefile文件中注释是#,并且一个文件中可以建立多个target。make默认的target是main。在 makefile 文件中也可以定义变量,通过 = 来设置,并且使用 ${} 或者 $() 来使用变量。
cmake
当工程量很大的时候,手写makefile又是一个很繁琐且枯燥的工作。如果换了一个平台,可能又得重新编写makefile(依赖库环境等不同导致)。如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用make工具,就得为每一种标准写一次makefile ,这将是一件让人抓狂的工作。因此,就衍生出来了cmake。其功能主要是通过cmake侦察工作环境,自动生成一个makefile文件,以便make工具使用。
除此之外,也有通过编写脚本去侦测工作环境。比如configure或config,侦测完成后,创建makefile文件。也有由QT提供的一个编译打包工具qmake,编译pro项目生成makefile文件。
使用步骤
在linux平台下使用CMake生成Makefile并编译的流程如下:
- 写
cmake配置文件CMakeLists.txt。 - 执行命令 
cmake PATH或者ccmake PATH或者cmake-gui生成Makefile(ccmake和cmake的区别在于前者提供了一个交互式的界面,而cmake-gui则是直接使用GUI界面操作)。其中,PATH是CMakeLists.txt所在的目录。 - 使用 
make命令进行编译。 

CMakeLists.txt文件编写规则
CMake的命令有不同类型,包括脚本命令、项目配置命令和测试命令,具体可以参考官网:CMake 教程 — CMake 3.26.0-rc5 文档
这里创建一个最简单的文件main.c,内容如下:
1  |  | 
在同一目录下创建CMakeLists.txt文件,且内容如下:
1  |  | 
说明:
cmake_minimum_required:指定运行此配置文件所需的CMake的最低版本;project:参数值是Demo_for,该命令表示项目的名称是Demo_for。add_executable:将名为main.c的源文件编译成一个名称为Demo的可执行文件。
cmake的使用
1  |  | 
说明,上述内容中,通过cmake指令对源程序main.c以及对应的CMakeLists.txt生成在该环境下的makefile,该过程中生成了一些CMakeCache等文件。其作用如下:
CMakeCache.txt:CMake中的缓存变量都会保存在CMakeCache.txt文件中。CMakeFIles/:包含由CMake在配置期间生成的临时文件。cmake_install.cmake:CMake脚本处理安装规则,并在安装时使用。
在Ubuntu中默认是不安装cmake的,安装如下:sudo apt-get install cmake
在使用
cmake过程中可以指定参数-G、--build等,其他具体内容也可以查看官网或者使用man cmake在系统中查看。
ccmake的使用

ccmake有两种用法:
- 方案一:通过直接对
CMakeLists.txt文件生成makefile文件。相对于cmake而言,其提供了一种字符界面的GUI操作面板。更加直观。 - 方案二:对
cmake操作过后的cache文件进行修改,重新生成makefile文件。 
方案一可以理解为从无到有的生成,而方案二则可以理解为修改默认配置文件,类似于去modify。
除此之外,ccmake包含在cmake-curses-gui包中,所以安装命令:sudo apt-get install cmake-cures-gui
这里首先对方案一进行展示使用过程:
1  |  | 
注意这里,当点击c后会自动配置内容,但是并不会出现[g]选项。这时候需要重新使用[c]选项,然后[e]退出后就会出现[g] Generate选项。
cmake-gui的使用
这个是一个基于QT的GUI界面。在Ubuntu中安装如下:sudo apt  install cmake-qt-gui,使用的时候直接命令:cmake-gui提供一个GUI界面。点击操作就行。

扩展内容
静态链接库&动态链接库
在C/C++中,项目最终都会分成两个部分内容,一个是头文件( .h ),一部分是源文件( .cpp) 。 如果要编写好的功能给其他程序使用,通常会把源文件打包形成一个动态链接库文件(.so .a)文件 。 值得注意的是,头文件一般不会打包到链接库中,因为头文件仅仅只是声明而已。而库是别人写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib) 和 动态库(.so、.dll)链接库也增加了代码的重用性、提高编码的效率,也可看看成是对源码的一种保护。
在
windows上库名对应的是.lib,.dll。而在linux上库对应的是.a,.so
库包含静态链接库和动态链接库两种。
静态链接库
在链接阶段,将源文件中用到的库函数与汇编生成的目标文件.o合并生成可执行文件。该可执行文件可能会比较大。
这种链接方式的好处是:方便程序移植,因为可执行程序与库函数再无关系,放在如何环境当中都可以执行。
缺点是:文件太大,一个全静态方式生成的简单print文件远大于动态链接生成的一样的可执行文件。(类似于一个是值传递,而另一个则是直接引用了地址而已,用的时候根据地址而加入该功能)。而且如果静态库liba.lib更新了,所以使用它的应用程序都需要重新编译、发布给用户。 
linux下的静态库文件是.a,而windows的静态库文件是.lib
动态链接库
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。也就是说程序在运行时由系统动态加载动态库到内存,供程序调用。
如何导入库
导入依赖库,需要导入两个部分的内容:头文件和源文件。源文件一般已经被打成了.so文件,所以实际上就是导入头文件和导入.so文件。
- 导入头文件:头文件一般会放置在一个文件夹
include中,可以把这个文件夹拷贝到工程内部,也可以放置在外部磁盘上,只需要指定地址找到它即可。 - 导入源文件(库文件):如果只导入了头文件,而没有到实现文件,那么会抛出异常,比如:xxx未定义之类的错误。因此需要导入对应的源文件(一般是
.so文件) 
库文件在链接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的。一般 Linux 系统把/lib和/usr/lib两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。但是,对于处于默认库搜索路径之外的库,就需要将库的位置添加到库的搜索路径之中。
可以将头文件路径和库文件路径添加到指定的系统环境变量中去,具体如下:
- 使用
gcc编译时将头文件路径添加到C_INCLUDE_PATH系统环境变量中; - 使用
g++编译时将头文件路径添加到CPLUS_INCLUDE_PATH系统环境变量中; - 将
动态连接库路径添加到LD_LIBRARY_PATH系统环境变量中; - 将
静态库路径添加到LIBRARY_PATH系统变量中。 
改变系统变量主要有两种形式,一种是临时改变,另一种是永久改变。临时改变系统变量只需要使用export命令,重启终端后将恢复至先前状态,如export C_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/myinclude(在原有基础上添加了/myinclude目录),而永久改变主要是将export命令写到系统文件中:如/etc/profile、/etc/export、~/.bashrc或者~/.bash_profile等等。
~/.bashrc在每次登陆和每次打开shell都读取一次,而~/.bash_profile只在登陆时读取一次。但是对于嵌入式Linux来说,有些文件可能没有,这就需要根据目标机器的情况来设置了。
对于/etc/profile、/etc/export文件中加入export命令,是对所有用户而言的,而~/.bashrc和~/.bash_profile是对当前用户起作用。
Linux中的交叉编译
就是说,假如我有个开发板,但是开发板上的资源很少(内存和存储空间),如果在开发板上又编译又链接又安装的,所需要的资源是很多的(编译过程产生的中间数据),因此,考虑使用一台单独电脑去编译和链接其源程序,然后将编译好的程序交给开发板,这样就可以减少开发板上编译链接所产生的数据。