【C++】为什么需要CMake?

文章目录

    • Cmake是什么?
    • Cmake是怎么出现的?
    • C++语言为什么会有头文件?
    • C++程序的编译流程
    • C++程序编译演化史

Cmake是什么?

很多C++小白刚进入公司开始接触业务代码的时候,或者在学校实验室接触一些大型的C++项目的时候,才发现有Cmake这个东西,一开始可能会有很大的疑惑:Cmake是什么?

而当你打开搜索引擎去检索Cmake的时候,搜索引擎告诉你Cmake仅仅是一个跨平台的编译工具,这时候你可能又会产生疑惑:为什么会有Cmake?C++还需要花费这么大精力去编译吗?之前在DEV,Visual Studio里明明一点“编译”,“运行”就能跑起来的程序,为什么现在还要去写Cmake去编译程序?有这么麻烦吗?

尽管当时的你有很多疑惑,但是你看不懂Cmake,你就无法适应你手上的工程,于是手头上面临的处境在逼迫你此时不得不了解Cmake,以满足生产环境的需要。而当你去找一些教程学习Cmake的时候,可能心里又会产生一个疑惑:为什么学习成本这么大?有必要搞这么麻烦吗?

事实上,如果你留心你所找的教程下面的评论或者公司里带你的师傅对你说过的话,你会发现他们好像都提过一句:Cmake不用学,用一用就会了。真相确实是这样,Cmake确实是用一用就会了。不信的话,你先去跟着一些教程去学一学,然后再来看这篇博文。

我们假定你现在已经系统学习了Cmake,那我现在要考一考你,如果说把你的工程里的CMake删除的话,你能重头到尾去写一个CMake吗?对于刚入门的C++程序员,我想你的答案应该是不能。是的,现在的你可能了解CMake中的每一个语法,每一个关键字,但是你还是无法写出一个CMake,原因在于现在的你不懂如何组织你的代码,这其实就是你看教程永远也学不会CMake的原因。而当你对你的工程比较熟悉之后,知道如何组织你的代码,这时候你就明白什么叫CMake不用学,用一用就会了。

但是这个过程是很艰难的。

Cmake是怎么出现的?

等你自己去写一个大型工程的时候,你会用到很多第三方库,你会需要把自己的代码打包成库,供他人使用,这时候,如果你还用学校里学的那一套:打开Visual Studio,新建文件,巴拉巴拉一大堆的时候,等到编译的时候,你就会知道为什么需要CMake。没有CMake肯定也能编译,但是那个复杂程度远超你的想象。

你的任务是把自己的工程跑起来,等你自己想办法仅仅为了绕开CMake去走传统的路子去跑你的程序的时候,你会发现,你在编译上花费的精力是巨大的,程序你已经写好了,却在最后一步编译上付出了很多努力,最后仅仅是为了运行,肯定是得不偿失的。

工具的出现是为了提高生产力,Cmake也是一样。现在的你可以这样想,在没有CMake时,已经有了这么一个人,走了刚刚你走过的路,但是等到他复盘的时候,发现自己的精力都花费在编译上了,这很不值得,于是,这个人想了一个办法,造出来了CMake这么个工具。

很多问题就是这样,你没有遇见过这个问题,所以你认为这个问题没有意义。CMake帮你解决了大型工程文件编译过程中的复杂问题,但是由于自身经验不足,你之前没有遇到过这种问题,所以一开始才会认为Cmake没有意义。

C++语言为什么会有头文件?

所有编程技术不断优化与升级的本质无外乎都是在做三件事情:

  1. 提升效率(程序运行相关)
  2. 减少重复
  3. 减轻依赖

我们试着反推这件事情,看看头文件是在解决什么问题。

#include 命令实际上只是将对应hpp中的文件原封不动的粘贴到#include所在位置,从这个角度而言,假如你的工程文件里有10个hpp,10个cpp,现在我们想砍掉所有的hpp文件,砍掉之后只剩下10个cpp文件,看上去我们的文件减少了,非常好,但是由于去掉了头文件,我们不得不做一些复制粘贴的工作:比如A.cpp中需要用到A.hpp中的add函数,由于砍掉了头文件,所以现在需要将hpp的声明原封不动的抄过来,还行,简简单单。然后你发现不光A.cpp中用到这个函数了,其他cpp文件中也遇到这个函数了,于是不得不再抄几份声明,抄来抄去发现:文件减少了,但是工作量却增加了!

这还只是一个函数,如果是一堆函数声明呢?

所以,现在明白了吗?我们可以借助头文件完成这些函数或者对象定义的快速复制粘贴,减少重复,从而提升工作效率。

就是这么简单,这些头文件在预处理阶段被展开到cpp中,之后的编译链接实际上用到的只是cpp文件。真正的编译单元只有cpp文件,头文件什么也不是,只是为了减少重复!

为什么会出现CMake?

要回答这个问题,我们需要先从源头说起。

C++程序是怎么编译的?

C++程序的编译流程

编译流程分为四个阶段:预处理、编译、汇编、链接

Linux系统下g++编译为例:

通过g++的选项可以查看过程中的每一步

img

  • 预处理: 处理一些#号定义的命令或语句(如#define、#include、#ifdef等),生成.i文件

  • 编译:进行词法分析、语法分析和语义分析等,生成.s的汇编文件

  • 汇编:将对应的汇编指令翻译成机器指令,生成二进制.o目标文件

  • 链接:调用链接器对程序需要调用的库进行链接。链接分为两种

    • 静态链接

      • 在链接期,将静态链接库中的内容直接装填到可执行程序中。

      • 在程序执行时,这些代码都会被装入该进程的虚拟地址空间中。

    • 动态链接

      • 在链接期,只在可执行程序中记录与动态链接库中共享对象的映射信息。

      • 在程序执行时,动态链接库的全部内容被映射到该进程的虚拟地址空间。其本质就是将链接的过程推迟到运行时处理

举个例子,现在仅仅给你一个main文件,里面只声明了add函数并调用了add函数,但是不包括add函数的实现,现在要对这个main文件进行编译运行,那么会走到哪一步?前三步走完都不会报错,直到遇到链接,才会报错,因为找不到add函数的实现。

你可以这样理解:每个文件都是独立编译的,直到链接开始前,这些文件被处理成.o文件,里面是一些机器码,等到链接的时候,这些文件才被组合起来。声明相当于你提前向你的程序做出了一份承诺:我现在有一个add函数,你在main文件里只管调用就行,我已经在其它文件里实现了!于是,main文件转头忙活自己的事,尽管它没见过add函数的实现,因为你已经向它做出了承诺。但是等到链接的时候才发现:根本没有add函数的实现。所以报错。

C++程序编译演化史

你可以写一份main文件,按照上面的流程,使用g++一步一步编译看看每一步会输出什么文件。你在整个过程中遇到的命令大概如下:

g++ -E main.cpp -o main.i
g++ -E add.cpp -o add.i
g++ -S main.i -o main.s
g++ -S add.i -o add.s
g++ -c main.s -o main.o
g++ -c add.s -o add.o
g++ main.o add.o -o main

当然,一般情况下我们会这么编译:

g++ main.cpp add.cpp -o main

现在引入一个问题:你有一个大的工程文件,里面有上百个cpp文件,分别存放在不同的目录中。这种情况如果用g++编译,其难度可想而知。于是,可以得出一个结论:g++对于大型工程并不是特别好用。

接着我们尝试做一些改进。

g++敲命令去编译,对于大型文件简直就是噩梦,每次编译都要敲一大串命令。但是写成文件总比敲命令要方便,要是把这些命令写成文件,每次编译的时候直接复用这个文件就好了。于是,Makefile诞生了。

上面的一大串命令写成对应的Makefile文件是这样的:

main: main.o add.og++ main.o add.o -o main
main.o add.o: main.s add.sg++ -c main.s -o main.og++ -c add.s -o add.o
main.s add.s:main.i add.ig++ -S main.i -o main.sg++ -S add.i -o add.s
main.i add.i: main.cpp add.cppg++ -E main.cpp -o main.ig++ -E add.cpp -o add.i

Makefile底层执行逻辑是只执行第一个命令,也就是:

main: main.o add.og++ main.o add.o -o main

但是由于没有main.o add.o ,所以才会向下执行。具体的语法有兴趣可以去查相关资料,这不是本文的侧重点。

嗯,看起来Makefile是g++的升级,我们可以借助Makefile文件实现相对于直接使用g++更快的编译。我们的问题被解决了吗?解决了一部分,但是还没完全解决。这次的问题出在跨平台上。

g++是Linux平台下的编译器,如果你使用win下的Visual Studio开发代码,它使用的是MSVC编译器,Makefile并不适用这种编译器,它跟Makefile相对应的文件是.sln文件。也就是说尽管大家都要经过预处理,编译,汇编,链接这四个步骤,但是大家走的路是不相同的。

于是,CMake就出现了。

Cmake本质上帮我们做的事情就是针对于不同的编译器,生成相对应的编译命令。针对Linux下的g++就是Makefile文件,针对win下的MSVC就是.sln文件。

CMake相当于在用户和操作系统上的编译器之间做了一层抽象,用户借助于CMake,不用关心自己的操作系统上用了什么编译器就能直接完成工程的快速编译。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/616567.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MATLAB 自定义实现点云法向量和曲率计算(详细解读)(64)

MATLAB 自定义实现点云法向量和曲率计算(详细解读)(64) 一、算法介绍二、算法步骤三、算法实现1.代码 (完整,注释清晰,可直接用)2.结果一、算法介绍 首先说明: ------这里代码手动实现,不调用matlab提供的法向量计算接口,更有助于大家了解法向量和曲率的计算方法,…

H2O-3机器学习平台源码编译的各种坑

H2O-3机器学习平台是一个非常适合非专业人士学习机器学习的平台,自带WebUI,效果还是蛮不错的,官方也提供了jar包,一条命令就能直接运行,非常方便,但最近有源码编译的需求,实际操作过程中&#x…

基于8B/10BGT收发器的PHY层设计(1)

一、PHY层简介 PHY层(Physical Layer)是OSI模型中最低的一层,也是最基本的一层,PHY是物理接口收发器,它实现物理层。包括MII/GMII(介质独立接口)子层、PCS(物理编码子层&#xff09…

ARM内核的CPU架构模型

1.引言 程序员在编码的时候,如果想有进一步的提升,我认为还是要深入底层理解程序运行原理才好。最近看了一些ARM架构的讲解,总结了如下。虽然是以ARM为原型画的图形,但是对于C和C的编程,还是有一些参考价值的。 2. AR…

Java 基于微信小程序的智能停车场管理小程序

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

Nexus 如何修改启动端口

默认的 Nexus 的启动端口为 8081。 如果我们使用这个启动端口可能会对我们其他的服务有冲突。 我们需要使用 Nexus 的默认启动端口。 修改文件 根据我们的安装配置为: /opt/nexusdata/nexus3/etc 目录下的 nexus.properties 文件。 注释掉 Jetty 的配置中有关端…

【leetcode】 跳跃游戏 IV

跳跃游戏IV 题目思路代码 题目 给你一个整数数组 arr &#xff0c;你一开始在数组的第一个元素处&#xff08;下标为 0&#xff09;。每一步&#xff0c;你可以从下标 i 跳到下标 i 1 、i - 1 或者 j &#xff1a;i 1 需满足&#xff1a;i 1 < arr.length i - 1 需满足&…

UBuntu18.04通过ODBC连接MySQL远程数据库

今天在做一个Qt视频播放器的小项目然后想要在ubuntu18.04运行这个项目&#xff0c;需要在Qt中连接远程的MySQL数据库&#xff0c;所以用到了ODBC。我在连接时遇到了一些问题&#xff0c;加之网上的教程各说纷纭&#xff0c;所以我花了很多时间去解决&#xff0c;所以决定做做笔…

pytorch车牌识别

目录 使用pytorch库中CNN模型进行图像识别收集数据集定义CNN模型卷积层池化层全连接层 CNN模型代码使用模型 使用pytorch库中CNN模型进行图像识别 收集数据集 可以去找开源的数据集或者自己手做一个 最终整合成 类别分类的图片文件 定义CNN模型 卷积层 功能&#xff1a;提…

Matlab之过球面一点的平面方程

这篇文章描述2件事情&#xff1a; 1、已知球面上任意点&#xff0c;求过该点、地心、与北极点的平面方程&#xff08;即过该点的经线平面方程&#xff09;&#xff1b; 2、绕过球心的任意轴旋转平面得到新平面的方程 一、已知球面上任意点&#xff0c;求过该点、地心、与北极点…

NPM 命令备忘单

NPM 简介 Node Package Manager (NPM) 是 Node.js 环境中不可或缺的命令行工具&#xff0c;充当包管理器来安装、更新和管理 Node.js 应用程序的库、包和模块。对于每个 Node.js 开发人员来说&#xff0c;无论他们的经验水平如何&#xff0c;它都是一个关键工具。 NPM 的主要…

MyBatis批量插入的五种方式

MyBatis批量插入的五种方式: 一、准备工作 1、导入pom.xml依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MySQL驱动依赖 --…