Linux对源码编译过程的探究之GCC&make&cmake

介绍

首先介绍一下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。主要区别在于:

  1. 对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的)
  2. 对于 .c和.cpp文件,g++则统一当做cpp文件编译
  3. 使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL
  4. gcc在编译C文件时,可使用的预定义宏是比较少的
  5. gcc在编译cpp文件时/g++在编译c文件和cpp文件时(这时候gcc和g++调用的都是cpp文件的编译器),会加入一些额外的宏。
    6.在用gcc编译c++文件时,为了能够使用STL,需要加参数 –lstdc++ ,但这并不代表 gcc –lstdc++ 和 g++等价,它们的区别不仅仅是这个。

gcc

gcc编译单个文件

> vim hello.c
#include<stdio.h>
int main(void){
    
    
	printf("hello,world.\n");
}

> gcc hello.c # 编译,会在当前生成名字为`a.out`的可执行二进制文件

> ./a.out

gcc编译两个文件

> vim main.c
include<stdio.h>
int main(void){
    
    
	double angle;
	printf("main function is exec...\n");
	printf("input the angle:");
	scanf("%lf",&angle);
	sin_value(angle);
}

> vim seconds.c
#include<stdio.h>
#include<math.h>

#define PI 3.141592653 

void sin_value(double angle){
    
    
	printf("seconds fun is exec ...\n");
	printf("angle = %f, and sin(angle)=%f\n",angle,sin(PI / 180 * angle));
}


> gcc -c main.c seconds.c # 编译两个源码文件,生成对应的目标文件(结尾为.o)

> gcc -o binary main.o seconds.o -lm # 链接两个目标文件,并生成可执行文件binary

> ./binary # 执行 
main function is exec...
input the angle:30
seconds fun is exec ...
angle = 30.000000, and sin(angle)=0.500000

说明:创建了两个源码文件,其中main.c文件中函数依赖于seconds.c文件中sin_value(double angle)函数。首先是编译过程,通过gcc -c将源码文件编译成目标文件main.oseconds.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

使用make编译

对于上述gcc编译两个文件例子中,如果每次修改了源码,都需要重新执行编译链接两个过程。如果一旦源码文件很多了,重复操作很繁琐。可以通过make来一个步骤完成(使用makefile文件然后使用make宏编译)。在一些最小安装的Linux系统中,默认是不安装make的(Ubuntu安装make:sudo apt-get install make)。在目录中创建一个makefile文件,且内容如下:

> vim makefile
main: main.o seconds.o
    gcc -o binary main.o seconds.o -lm
clean:
    rm -f binary main.o seconds.o
# 注意在每个targets(main,clean)下面一行都有个<tab>.

> make # 编译,也就是执行main的指令,等价于make main

> make clean # 清理,也就是执行clean下的指令。

注意,默认使用的文件名为makefile,当然可以在make时候通过添加参数-f指定文件名,比如Makefile文件编译可通过:make -f Makefile clean,并且如果makefile文件内容很多,导致运行过程中不知道有多少个targets,可以通过make <tab>来通过Bash补全。

makefile语法

makefile的基本语法规则为:

target: 目标文件1 目标文件2...
	gcc -o 可执行文件的名字 目标文件1 目标文件2... 等

makefile文件中注释是#,并且一个文件中可以建立多个targetmake默认的target是main。在makefile文件中也可以定义变量,通过 = 来设置,并且使用 ${} 或者 $() 来使用变量。

cmake

当工程量很大的时候,手写makefile又是一个很繁琐且枯燥的工作。如果换了一个平台,可能又得重新编写makefile(依赖库环境等不同导致)。如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用make工具,就得为每一种标准写一次makefile ,这将是一件让人抓狂的工作。因此,就衍生出来了cmake。其功能主要是通过cmake侦察工作环境,自动生成一个makefile文件,以便make工具使用。

除此之外,也有通过编写脚本去侦测工作环境。比如configureconfig,侦测完成后,创建makefile文件。也有由QT提供的一个编译打包工具qmake,编译pro项目生成makefile文件。

使用步骤

linux平台下使用CMake生成Makefile并编译的流程如下:

  1. cmake配置文件CMakeLists.txt
  2. 执行命令 cmake PATH 或者 ccmake PATH 或者cmake-gui生成 Makefileccmakecmake 的区别在于前者提供了一个交互式的界面,而cmake-gui则是直接使用GUI界面操作)。其中, PATHCMakeLists.txt 所在的目录。
  3. 使用 make 命令进行编译。
    在这里插入图片描述

CMakeLists.txt文件编写规则

CMake的命令有不同类型,包括脚本命令、项目配置命令和测试命令,具体可以参考官网:CMake 教程 — CMake 3.26.0-rc5 文档

创建一个最简单的文件main.c,内容如下:

#include <stdio.h>
int main(){
    
    
	for(int i = 1; i < 100; ++i){
    
    
		printf("%d\n",i);
	}
}

在同一目录下创建CMakeLists.txt文件,且内容如下:

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (Demo_for)

# 指定生成目标
add_executable(Demo main.c)
  1. cmake_minimum_required:指定运行此配置文件所需的CMake的最低版本;
  2. project:参数值是 Demo_for,该命令表示项目的名称是 Demo_for
  3. add_executable:将名为main.c的源文件编译成一个名称为Demo的可执行文件。

cmake的使用

> ls
CMakeLists.txt  main.c

> cmake .   # <=== 制作makefile开始,在当前文件夹下找CMakeLists.txt文件
CMake Deprecation Warning at CMakeLists.txt:2 (cmake_minimum_required):
  Compatibility with CMake < 2.8.12 will be removed from a future version of
  CMake.

  Update the VERSION argument <min> value or use a ...<max> suffix to tell
  CMake that the project does not need compatibility with older versions.


-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /cmake

> ll  # 编译完成后新增加内容。
-rw-rw-r-- 1 user user 13785  35 15:00 CMakeCache.txt
drwxrwxr-x 5 user user  4096  35 15:00 CMakeFiles/
-rw-rw-r-- 1 user user  1634  35 15:00 cmake_install.cmake
-rw-rw-r-- 1 user user   150  35 14:55 CMakeLists.txt
-rw-rw-r-- 1 user user    87  35 14:48 main.c
-rw-rw-r-- 1 user user  5065  35 15:00 Makefile

说明,上述内容中,通过cmake指令对源程序main.c以及对应的CMakeLists.txt生成在该环境下的makefile,该过程中生成了一些CMakeCache等文件。其作用如下:

  1. CMakeCache.txtCMake中的缓存变量都会保存在CMakeCache.txt文件中。
  2. CMakeFIles/:包含由CMake在配置期间生成的临时文件。
  3. cmake_install.cmakeCMake脚本处理安装规则,并在安装时使用。

在Ubuntu中默认是不安装cmake的,安装如下:sudo apt-get install cmake

在使用cmake过程中可以指定参数-G--build等,可以查看官网或者使用man cmake在系统中查看。

ccmake的使用

在这里插入图片描述

ccmake有两种用法:

  1. 通过直接对CMakeLists.txt文件生成makefile文件。相对于cmake而言,其提供了一种字符界面的GUI操作面板。更加直观。
  2. cmake操作过后的cache文件进行修改,重新生成makefile文件。

用法1可以理解为从无到有的生成,而用法2可以理解为修改默认配置文件,类似于修改。
ccmake包含在cmake-curses-gui包中,所以安装命令:sudo apt-get install cmake-cures-gui

这里对方案1进行展示

> ls
CMakeLists.txt  main.c

> ccmake .
EMPTY CACHE

EMPTY CACHE: 
Keys: [enter] Edit an entry [d] Delete an entry 
CMake Version 3.22.1
      [l] Show log output   [c] Configure
      [h] Help              [q] Quit without generating
      [t] Toggle advanced mode (currently off)

> c  # <==键盘输入c表示配置

CMake Deprecation Warning at CMakeLists.txt:2 (cmake_minimum_required):
   Compatibility with CMake < 2.8.12 will be removed from a future version of
   CMake.

   Update the VERSION argument <min> value or use a ...<max> suffix to tell
   CMake that the project does not need compatibility with older versions.

 The C compiler identification is GNU 11.3.0
 The CXX compiler identification is GNU 11.3.0
 Detecting C compiler ABI info
 Detecting C compiler ABI info - done
 Check for working C compiler: /usr/bin/cc - skipped
 Detecting C compile features
 Detecting C compile features - done
 Detecting CXX compiler ABI info
 Detecting CXX compiler ABI info - done
 Check for working CXX compiler: /usr/bin/c++ - skipped
 Detecting CXX compile features
 Detecting CXX compile features - done
 Configuring done

> g #<==生成,这里有个问题,得重复按两下c才会出现[g]选项。
                                                     Page 1 of 1
 CMAKE_BUILD_TYPE
 CMAKE_INSTALL_PREFIX             /usr/local

CMAKE_BUILD_TYPE: Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel ...  
Keys: [enter] Edit an entry [d] Delete an entry 
CMake Version 3.22.1
      [l] Show log output   [c] Configure       [g] Generate
      [h] Help              [q] Quit without generating
      [t] Toggle advanced mode (currently off)

注意这里,当点击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文件。

  1. 导入头文件:头文件一般会放置在一个文件夹include中,可以把这个文件夹拷贝到工程内部,也可以放置在外部磁盘上,只需要指定地址找到它即可。
  2. 导入源文件(库文件):如果只导入了头文件,而没有到实现文件,那么会抛出异常,比如:xxx未定义之类的错误。因此需要导入对应的源文件(一般是.so文件)

库文件在链接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的。一般 Linux 系统把/lib/usr/lib两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。但是,对于处于默认库搜索路径之外的库,就需要将库的位置添加到库的搜索路径之中。

将头文件路径和库文件路径添加到指定的系统环境变量中去,具体如下:

  1. 使用gcc编译时将头文件路径添加到C_INCLUDE_PATH系统环境变量中;
  2. 使用g++编译时将头文件路径添加到CPLUS_INCLUDE_PATH系统环境变量中;
  3. 动态连接库路径添加到LD_LIBRARY_PATH系统环境变量中;
  4. 静态库路径添加到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是对当前用户起作用。

猜你喜欢

转载自blog.csdn.net/weixin_41012765/article/details/129405232