之前在学C++ 的时候,一直不懂:动态内存分配的本质,或者更加深刻到底层的意义.虽说,动态内存分配就是,随机在内存中分配一个地址,可是这句话包含的内容实在太多了,不去理解底层的话,不管如何也仅仅停留在表面阶段,永远提升不了.今天下午,忽然看到很久之前的QT5,基本半年没研究过了,仍是之前没事干瞎作播放器的时候安装的,因而来了兴趣,想玩玩看.程序员
可是,我遇到了一个问题,就是始终不懂QT框架这么设计的核心,或者从操做系统级别来说,它为何提供这样的方式?编程
请看代码,最简单的默认项目;微信
#include <QApplication>#include <MainWindow> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
这是一个最简单的程序:就是显示一个窗口,恐怕不少人已经习觉得常了吧.架构
可是,我此时有问题要问:app
#include <QApplication>#include <MainWindow> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow *w=new MainWindow; w->show(); return a.exec(); }
这个程序运行起来,和上面是彻底同样的,可是请问:有什么问题?也许不少人一口就说,没问题啊,不就是和上面同样吗?框架
细心的人发现有不一样异步
而后,咱们在看一下这个代码:函数
#include <QApplication>#include <QPushButton>#include <mainwindow.h>#include <QDebug>int main(int argc, char *argv[]) { QApplication app(argc, argv); QPushButton bt1; QPushButton *bt2=new QPushButton; MainWindow w1; MainWindow *w2=new MainWindow; qDebug() << "bt1:%d " << &bt1; qDebug() << "bt2:%d " << bt2; qDebug() << "w1:%d " << &w1; qDebug() << "w2:%d " << w2; return app.exec(); }
咱们来测试一下内存分配结果:性能
注意:系统分配法和动态分配法地址长度不同!测试
一个用了直接定义法,一个用了指针写法,问题就是:每一个人都能说:后面的是动态内存分配,前面的是操做系统分配.还有呢?动态内存分配究竟怎么个分配法?
或许不少人学C++的时候,也是面对这两种写法 很懵逼,貌似同样吧,可是本质又不同.一直在表面逗留,而深刻不到本质去.
我这里,从更加底层开始分析.咱们首先从芯片结构来讲起.有人以为C++怎么会和芯片结构有关系?别忘了,C++继承了C的全部.
好了,开始说芯片:咱们从最简单的51单片机提及.
下面是百科:
8051是MCS-51系列单片机的典型产品,咱们以这一表明性的机型进行系统的讲解。
8051单片机包含中央处理器、程序存储器(ROM)、数据存储器(RAM)、定时/计数器、并行接口、串行接口和中断系统等几大单元及数据总线、地址总线和控制总线等三大总线,如今咱们分别加以说明:
· 中央处理器:
中央处理器(CPU)是整个单片机的核心部件,是8位数据宽度的处理器,能处理8位二进制数据或代码,CPU负责控制、指挥和调度整个单元系统协调的工做,完成运算和控制输入输出功能等操做。
· 数据存储器(RAM):
8051内部有128个8位用户数据存储单元和128个专用寄存器单元,它们是统一编址的,专用寄存器只能用于存放控制指令数据,用户只能访问,而不能用于存放用户数据,因此,用户能使用的的RAM只有128个,可存放读写的数据,运算的中间结果或用户定义的字型表。
· 程序存储器(ROM):
8051共有4096个8位掩膜ROM,用于存放用户程序,原始数据或表格。
· 定时/计数器(ROM):
8051有两个16位的可编程定时/计数器,以实现定时或计数产生中断用于控制程序转向。
· 并行输入输出(I/O)口:
8051共有4组8位I/O口(P0、 P一、P2或P3),用于对外部数据的传输。
· 全双工串行口:
8051内置一个全双工串行通讯口,用于与其它设备间的串行数据传送,该串行口既能够用做异步通讯收发器,也能够当同步移位器使用。
· 中断系统:
8051具有较完善的中断功能,有两个外中断、两个定时/计数器中断和一个串行中断,可知足不一样的控制要求,并具备2级的优先级别选择。
· 时钟电路:
8051内置最高频率达12MHz的时钟电路,用于产生整个单片机运行的脉冲时序,但8051单片机需外置振荡电容。
· 时钟电路:
8051内置最高频率达12MHz的时钟电路,用于产生整个单片机运行的脉冲时序,但8051单片机需外置振荡电容。
单片机的结构有两种类型,一种是程序存储器和数据存储器分开的形式,即哈佛(Harvard)结构,另外一种是采用通用计算机普遍使用的程序存储器与数据存储器合二为一的结构,即普林斯顿(Princeton)结构。INTEL的MCS-51系列单片机采用的是哈佛结构的形式,然后续产品16位的MCS-96系列单片机则采用普林斯顿结构。
注意:重点在这里
数据存储器?程序存储器?这是什么?和C++动态呢诶村分配又有什么关系?
那咱们就来抓一下头绪.
下面是百科,关于程序区和数据区:
C语言可执行代码结构
名称 |
内容 |
代码段 |
可执行代码、字符串常量 |
数据段 |
已初始化全局变量、已初始化全局静态变量、局部静态变量、常量数据 |
BSS段 |
未初始化全局变量,未初始化全局静态变量 |
栈 |
局部变量、函数参数 |
堆 |
动态内存分配 |
通常状况下,一个可执行二进制程序(更确切的说,在Linux操做系统下为一个进程单元,在UC/OSII中被称为任务)在存储(没有调入到内存运行)时拥有3个部分,分别是代码段(text)、数据段(data)和BSS段。这3个部分一块儿组成了该可执行程序的文件。
(1)代码段(text segment):存放CPU执行的机器指令。一般代码段是可共享的,这使得须要频繁被执行的程序只须要在内存中拥有一份拷贝便可。代码段也一般是只读的,这样能够防止其余程序意外地修改其指令。另外,代码段还规划了局部数据所申请的内存空间信息。
代码段(code segment/text segment)一般是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经肯定,而且内存区域一般属于只读, 某些架构也容许代码段为可写,即容许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
(2)数据段(data segment):或称全局初始化数据段/静态数据段(initialized data segment/data segment)。该段包含了在程序中明确被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)和常量数据。
(3)未初始化数据段:亦称BSS(Block Started by Symbol)。该段存入的是全局未初始化变量、静态未初始化变量。
而当程序被加载到内存单元时,则须要另外两个域:堆域和栈域。图1-1所示为可执行代码存储态和运行态的结构对照图。一个正在运行的C程序占用的内存区域分为代码段、初始化数据段、未初始化数据段(BSS)、堆、栈5个部分。
图1-1 C语言可执行代码结构
(4)栈段(stack):存放函数的参数值、局部变量的值,以及在进行任务切换时存放当前任务的上下文内容。
(5)堆段(heap):用于动态内存分配,即便用malloc/free系列函数来管理的内存空间。
在将应用程序加载到内存空间执行时,操做系统负责代码段、数据段和BSS段的加载,并将在内存中为这些段分配空间。栈段亦由操做系统分配和管理,而不须要程序员显示地管理;堆段由程序员本身管理,即显示地申请和释放空间。
另外,可执行程序在运行时具备相应的程序属性。在有操做系统支持时,这些属性页由操做系统管理和维护。
重点:堆栈!
先来看51单片机:
大概就长这样,最简单的微型计算机,它里面包含了256个地址,每一个地址对应一个汇编指令,而后经过这些指令来进行运做.关于单片机的问题,能够本身去看相关内容.
如今的疑问就是:什么事堆栈?
对于单片机来说,能够简单的理解为:片内,片外存储.片内指的是,单片机的自身内存,很是小,片外值得是ROM,一班有8KB.能够看出是很是小的.
为何要扯着么多:为了你后面理解C++指针.
你们都知道,C语言的基础数据类型int ,char,double......等等,这些数据类型,是内置的,也就是说,已经有他们的地址存在了,若是放在但联机里,那就是,预先在片内给他们一个地址.可是,咱们的自定义类型呢?好比结构体,自定义后,并非系统能够知道的数据类型,因此要进行动态内存分理,一班会在程序区给它分配一个地址,看下面的代码:
int a=6;//定义一个a,值6,此时它是在系统栈里面的地址.
int *b=&a; //b拿到a的地址,此时b就能够操做a所在的内存了
由于int是预约义的数据类型,因此,在操做系统栈区分配内存就很快,可是换作自定义的类型的话,就比较慢了:
看这个QT代码:
#include <QApplication>#include <MainWindow> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow *w=new MainWindow; w->show(); return a.exec(); }
此时MainWindow 是一个自定义类型,若是这样写,会在程序区申请空间,可是若是关闭窗口的时候,没有释放内存,会形成内存泄漏由于[w->show()]完了之后就没有释放操做了,为了不,应该用操做系统栈的分配方法,这样把窗口的操做控制权交给系统,能够避免内存泄漏.
看了上面的例子,咱们来总结一下:
什么是操做系统栈:就是一个内存区,这个区是操做系统使用的,负责系统使用变量地址的建立.
是么是程序运行堆:application运行的时候使用的内存区域,好比QQ,微信等等,都是在程序运行区运行的.
咱们列举了单片机的特性,单片机内存很小,为了防止溢出,因此有时候要向片外申请地址,此时动态内存分配就派上用场了.
换言之,曾今几十年前C语言刚出来的时候,机器的性能也就今天的51单片机的性能,为了优化机器运行,因此要进行动态内存分配.
深刻理解指针系列,仍是要从最原来的含义开始提及,若是从硬件的内部进行研究的话,我以为理解指针和动态内存分配也就不难了.