通过前面两节课的内容,我带领大家熟悉了一下 Visual Studio C++ 开发环境的必备知识,虽然还有很多关于 Visual Studio 的重要知识没有介绍,但为了让你尽快进入 C++ 开发环节,及早获得开发程序的愉悦,我们暂时只介绍这些必备知识。

C++ 创建至今已有近 40 年的历史,经历了数次重大的变革,本贾尼老爷子自己也说现在的 C++ 就像一门全新的语言。不过 C++ 终究是基于 C 发展而来的,时至今日 C++ 还是和 C 保持着最大的兼容性。

当前的软件世界还有很多项目都是基于 C 或者早期版本的 C++ 构建而来的,随着时间的发展它们已经成为了当今计算机软件世界的基础设施,比如 Windows 操作系统、 SDK 、SQLite 、FFmpeg 等。我们使用现代 C++ 开发软件项目往往要依赖这些基础设施,如果只使用现代 C++ 特性而不了解底层原理,那么可能无法很好地使用这些基础设施。

从本节开始,我将带领你学习现代 C++ 语言及相关知识。首先介绍 C++ 的编译流程,了解这些知识之后你就知道 C++ 的头文件和源码文件是如何被编译成二进制可执行文件的了。接着会讲解一个应用程序运行期的内存布局,了解了这些知识之后,你就知道在代码中的那些变量到底存储在用户内存的哪个区域了。

这些知识都是开发 C++ 应用程序的必备知识,掌握了这些知识再去学习 C++ 的语法就会更加从容。

C++ 编译流程

如果看过 C++ 项目的代码,你就会发现,项目中分为两种文件:头文件(.h)源码文件(.cpp)。不像 JavaScript 项目,只有 js 文件,为什么 C++ 项目要把代码写在两种不同的文件中呢?

这里不讨论 .mm、.hpp 等扩展名的 C++ 文件。

这要从 C++ 的编译流程说起。在 C++ 项目中,头文件和源码文件往往是成对儿出现的,我们把一对儿 .h 和 .cpp 文件称为一个代码单元,源码文件通过#include指令引入与自己对应的头文件,头文件和源码文件也可以通过#include指令引用另一个代码单元的头文件,以获得另一个代码单元的能力。

无论是头文件还是源码文件,最终都会被编译到可执行文件中,整个过程如下图所示:

前端开发者的现代 C++ 课 - 刘晓伦liulun - 掘金小册专门为前端开发者“定制”的现代 C++ 编程指南。「前端开发者的现代 C++ 课」由刘晓伦liulun撰写,490人购买https://s.juejin.cn/ds/kUo6RHC/

在上图中,Class2 的头文件引入了(#include) Class1 的头文件,Class2 的源文件引入了 Class3 的头文件。

在 Visual Studio 编译程序时,第一个环节:预处理环节,就是用来处理这些引用关系的,在这个环节 Visual Studio 会把 #include "Class1.h"这行的代码替换成Class1.h文件中的全部内容。

注意:Class1.h中往往只包含类型、方法、变量的声明而不包含实现逻辑。不过 C++ 规定只要给出类型、方法、变量的声明,就可以使用它们,所以这里的作用只是让 Class2 得到 Class1 在头文件中声明的内容,得到了这些声明之后,编译器就不会出现语法错误(比如:使用了未定义的变量或方法)。此时的 Class2 是不知道 Class1 的具体实现的。

预处理环节除了完成#include指令的替换工作之外,还完成了条件编译指令#if #elif #endif的处理工作、常量的替换工作等。

完成预处理环节的工作之后,Visual Studio 开始执行编译环节的工作,在这个环节编译器经过词法分析、语法分析、语义分析、代码优化、汇编等过程把各个类(或程序单元)编译成机器指令。

在这个过程中,如果你的程序有一些编译器能发现的错误,Visual Studio 则会提示编译异常。

最终生成的机器指令被存放在一系列的 .obj 文件中,你可以在[YourSolutionDir]\x64\Debug 目录下找到这些文件。

完成编译环节的工作之后,Visual Studio 开始执行链接环节的工作,链接器会把上一个环节生成的所有 obj 文件,还有标准库的 lib 文件、第三方库的 lib 文件链接到一起,最终生成可执行文件或动态链接库(.exe 文件或.dll 文件)。

只有链接工作执行完成之后,Class1 的实现逻辑才和 Class2 的实现逻辑绑定到一起,Class2 才可以真正地访问 Class1 的方法。

由此可见,C++ 使用头文件和源码文件一定程度上起到了隐藏实现细节、控制访问权限的目的。之所以 C++ 要在两个文件中完成这项工作,主要是为了适应配编译器的要求。

预处理指令 #pragma once

现代 C++ 头文件中往往会用到了一个预处理指令:#pragma once这个指令告诉预处理器这个文件只会被处理一次

比如,Class1.h 引入了 Class2.h 和 Class3.h ,而 Class3.h 的头文件也引入了 Class2.h 的头文件,如下图所示:

前面我们说了,#include 语句会被替换成被包含的文件,那么如果不加处理的话,最终 Class1.h 中将包含两份 Class2.h 的内容。

如果 Class2.h 中包含#pragma once这个预处理器指令,则在预处理器处理 Class1.h 的头文件时,则只会包含一次 Class2.h 的内容,不会因为 Class3.h 也包含 Class2.h 的引用就会在 Class1.h 中创建两份 Class2.h 的内容。

这是现代 C++ 编译器新增的一个指令,这个指令出现之前,C++ 开发者都是通过如下方式来保证头文件不会被重复编译的。

#ifndef _FileA
#define _FileA
// code
#endif

这种方式书写起来繁琐,编译时也低效,所以推荐使用#pragma once指令。一般情况下,C++ 头文件中都会使用和加入 #pragma once 指令。

应用程序内存布局

一个应用程序在操作系统中运行时,它占用的内存一般分为以下几个区域。

  • 内核空间:用于存储操作系统和驱动程序为进程提供的临时机器指令和中间变量。
  • 映射段:用于装载或映射动态链接库,也常用于将文件内容映射到内存中。
  • 代码段:用于存放应用程序的机器指令,为了防止指令被其他程序修改,代码段是只读的。
  • 数据段:用于存储全局变量、静态变量(static)和常量数据(const)。
  • :用于存储应用程序运行过程中申请的内存空间,比如使用 malloc 方法或 new 关键字申请的内存。
  • :用于存储函数的局部变量、参数、返回值及调用者的上下文信息。

如下图所示:

一个进程真正的内存使用情况并不像上图中描述的这样规整,每个区块的大小差异巨大,而且不同类别的内存可能会交叉出现,不同的内存区间也可能是不连续的、碎片化的。你应该关注数据段、堆和栈这三个内存区域,后文中我们还会反复提到这些概念。

JavaScript 的解释引擎 V8 也遵循这个内存布局约定,但 JavaScript 并不遵循这个约定,因为 JavaScript 是运行在 V8 之上的,由 V8 定义 JavaScript 的内存布局模型。

栈与栈帧

我们先来简单介绍一下栈与栈帧的用途,如下图所示:

当你的程序进入 main 方法后,程序将在空间中创建一个栈帧,变量 a 和 b 保存在这个栈帧中;当 main 方法调用 method1 时,程序将在空间中创建第二个栈帧,变量 c 和 d 保存在第二个栈帧中;当 method1 调用 method2 时会执行类似的工作,当 method2 方法返回时,method2 的栈帧会被销毁,栈帧上保存的变量也会被销毁。method1 返回时也会执行同样的销毁工作。

如果你在 method1 中调用 method2 时传递了方法参数,很多时候这些参数也会被拷贝到 method2 的栈帧中,在 method2 中修改这些参数,只是在修改 method2 栈帧上的参数副本,并不会影响 method1 中的对应变量。

栈帧空间切分成了很多片,每个方法享有独立的内存空间,除非专门的设置(后文会讲:引用参数),一个方法不会更改另一个方法的栈帧内存。

空间中分配内存的变量不需要程序员手动销毁,栈帧销毁时栈帧上的变量会被自动销毁。

栈的总内存大小是固定的,而且非常小,在 Windows 操作系统中默认大小为 1M,当在栈空间中申请的内存超过栈的剩余空间时,将提示内存溢出错误。这也是为什么开发者要关注递归调用引发栈溢出的原因(递归调用会创建非常多的栈帧)。

操作系统会专门分配寄存器存放栈的地址,入栈、出栈都有专门的指令执行,所以操作栈上的内存效率非常高。

如果想让某个变量在函数调用结束之后仍然可用,那么可以在空间中为变量分配内存,使用 new 操作符就可以完成这项工作,new 操作符返回堆空间的地址(就是指针)。除非专门的设置(后文会讲:智能指针),开发者必须自己完成堆内存的释放工作,使用 delete 关键字可以完成这项工作(C++ 中的 delete 关键字与 JavaScript 中的 delete 关键字差异巨大)。

堆内存的大小与计算机系统中有效虚拟内存大小有关,比栈空间要大得多,大部分时候开发者都会把大对象或数组放到堆中

堆内存没有专门的优化,使用效率较低,且容易产生内存碎片。

总结

本节我们首先介绍了 C++ 的编译流程,编译工具在预处理环节替换了所有的 #include 指令,编译工具在链接环节把各个编译单元的二进制代码链接到一起,这样 C++ 的头文件和源码文件就正式被编译成二进制可执行文件了。

接着我们讲解了应用程序的内存布局,其中最重要的是:数据段、堆和栈。数据段用于存储全局变量和静态变量;堆用于存储应用程序运行过程中使用 new 或 malloc 申请的内存空间;栈用于存储函数的局部变量、参数、返回值及调用者的上下文信息。

这些知识是 C++ 开发的基础知识,掌握了这些知识之后我们再开启 C++ 编码之旅就会少一些疑惑,多一些勇往直前的勇气。

前端开发者的现代 C++ 课 - 刘晓伦liulun - 掘金小册专门为前端开发者“定制”的现代 C++ 编程指南。「前端开发者的现代 C++ 课」由刘晓伦liulun撰写,490人购买https://s.juejin.cn/ds/kUo6RHC/

本立道生:必备的基础知识相关推荐

  1. 安防监控必备的基础知识

    安防监控必备的基础知识 什么是云镜控制解码器? 答:解码器是将前端发出的控制信号转换为电压信号从而控制云台.镜头的的装置. 什么是同轴电缆? 答:同轴电缆(COARIAL CABLE)的得名与它的结构 ...

  2. python十大必备知识_学Python必备的基础知识

    学Python必备的基础知识 1.基本概念 表达式:就是一个类似于数学公式的东西,一般仅仅用了计算一些结果 ,不会对程序产生实质性的影响,如9+3; 语句:在程序中语句一般需要完成某种功能,比如打印信 ...

  3. (Java高级教程)第四章必备前端基础知识-第二节1:CSS概述和选择器

    文章目录 一:CSS概述 (1)概述 (2)语法规范 (3)CSS引入方式 二:选择器 (1)基础选择器 ①:标签选择器 ②:类选择器 ③:id选择器 ④:通配符选择器 总结 (2)复合选择器 ①:后 ...

  4. 数控技术复习(二):数控编程必备的基础知识

    文章首发于个人博客,欢迎访问:数控技术复习(二):数控编程必备的基础知识 数控机床加工零件:零件图代码->程序单->控制介质->数控装置->伺服电机->机床自动加工.从零 ...

  5. 学计算机设计制图需啥基础,学好室内设计制图必备的基础知识

    学好室内设计制图必备的基础知识 导语:学室内设计除了熟练掌握计算机辅助软件以外,更要加强自身的素质.我认为室内设计的最重要的两部分,首先是功能设计,下一步才是效果设计,因为我在做效果设计以前一直是做功 ...

  6. 程序员必备计算机基础知识总结电子书下载

    程序员必备计算机基础知识总结电子书下载 日常 9分钟前 2阅读0点赞0评论 给大家推荐一本超级经典的计算机基础知识的书! 这本书主要是程序员必知的硬核基础知识,非常经典的入门书籍,小编吧内容看了适合看 ...

  7. union连接攻击MySQL函数_sql注入之必备的基础知识

    什么是sql注入(sql injection) 所谓sql注入式攻击,就是攻击者把sql命令插入到web表单的输入域或页面请求的查询字符串,欺骗服务器执行恶意的sql命令.在某些表单中,用户输入的内容 ...

  8. mysql 立方根函数_PostgreSQL基础知识之SQL操作符实践指南

    前言 操作符是数据库具有的运算特性,对文本字符和一些标签字符的处理,使用操作符可以简化SQL代码,同时也可以节省开发时间和提高开发效率. 比较操作符 在PostgreSQL中,所有的数据类型都可以使用 ...

  9. MySQL--新手必备SQL基础知识、事务ACID及隔离级别

    ❤️‍您好,我是贾斯汀,本文主要分享数据库的一些基础知识!❤️‍ SQL 什么是SQL? [百度百科] 结构化查询语言(Structured Query Language)简称SQL,是一种特殊目的的 ...

  10. JavaScript 面试必备的基础知识梳理(71个知识点)

    1. JavaScript简介 JavaScript 最开始是专门为浏览器设计的一门语言,但是现在也被用于很多其他的环境. 如今,JavaScript 已经成为了与 HTML/CSS 完全集成的,使用 ...

最新文章

  1. 关于机器翻译的三个话题的讨论
  2. Spring和springmvc两个容器的关系
  3. opensips和pbx之间的连接
  4. JeecgBoot 3.1.0 版本发布,基于代码生成器的企业级低代码平台
  5. 在Ubuntu和CentOS上搭建NodeJs的执行环境步骤
  6. oracle 和mysql有什么区别_mysql和oracle的区别有哪些
  7. 锐捷客户端在Linux下的使用。
  8. Fortran95学习笔记
  9. 成都职称计算机 报几科,成都2018年7月上职称计算机考试报名事项通知
  10. 老一辈学计算机的在那,真实的南京大学计算机系
  11. html 磁贴自动布局,也来“玩”MetroUI之磁贴(一)_html/css_WEB-ITnose
  12. 《Two-Archive Evolutionary Algorithm for Constrained Multiobjective Optimization》阅读笔记
  13. matlab2019b classification learner使用笔记
  14. Log与logcat
  15. 游戏数值策划入门介绍
  16. linux全盘扫描,3个有用的基于GUI和终端的Linux磁盘扫描工具
  17. matlab画多组数据折线图_使用Origin绘制不相关多组数据折线图的方法
  18. VSS2005+vs2012配置
  19. 在 EXCEL 中,“插入已剪切单元格”的快捷键
  20. 水平集详解与代码分析一

热门文章

  1. Python——类和对象、魔术方法(day07)
  2. 零基础学php rar,php实现rar文件的读取和解压
  3. android Wifi热点启动流程,[android]WIFI热点启动流程分析
  4. 第三方支付的流程分析与总结
  5. 软文管家发布平台_企业软文如何做好
  6. P4717-[模板]快速莫比乌斯/沃尔什变换(FMT/FWT)
  7. SQL练习题附重点函数说明--更新至21题
  8. html点击出现对勾,css伪类 右下角点击出现 对号角标表示选中的示例代码
  9. 浏览器标准模式和怪异模式之间的区别是什么?
  10. 该填志愿了,国内大学计算机专业哪家强?