编译初探究之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中的交叉编译
就是说,假如我有个开发板,但是开发板上的资源很少(内存和存储空间),如果在开发板上又编译又链接又安装的,所需要的资源是很多的(编译过程产生的中间数据),因此,考虑使用一台单独电脑去编译和链接其源程序,然后将编译好的程序交给开发板,这样就可以减少开发板上编译链接所产生的数据。