堆、栈与大小端存储
前言
首先先提一个和操作系统主引导相关的概念:一个有效的主引导扇区,其起始地址为0X7c00,最后两个自己的数据必须是0x55、0xaa;否则这个扇区里保存的就不是一些有意而为的数据。这三个数都是所谓的"魔数",而0x7c00的由来是这样的:虽然一个Mbr大小为512字节,但是加上堆栈区大小应该为1KB左右。而在8086CPU中0x0~0x3ff
是存放中断向量表的,按照DOS1.0的32KB标准,我们最好将其放置在末尾处,大小占1KB,所以起始位置为31KB处,即0x7c00.然后再看0x55与0xaa,如果是两个单独的字节,那么低地址放0x55,高地址放0xaa;但是如果是一个字单位,呢就应该是0xaa55
,这里涉及到了大小端字节序。
但是自己突然又有个问题,这里的存放顺序是大小端问题,但是数据存储又分为存在堆与栈中,这两者是怎么联系起来的?自己以前写过关于结构体内存存放的一篇文章,现在看来当时还没有想到与堆栈之间的联系,那现在来看一下数据存放与堆栈之间的关系。
大小端存储
这个概念在网络编程中应该会涉及到,虽然现在各种服务器模型和框架(事实上在设计socket网络编程之初就已经考虑了这个问题)都已经有了非常好的处理大小端存储的方式,htonl、ntohl....
;但是了解一下为什么这么做也是比较重要的:
简单来说,从低地址往高地址(一般visual studio提供的内存查看方式都是这样,我们就采用这种描述方式)来看,小端会将数据的低字节部分先放入,然后放高字节部分,就比如int x=0x12345678
,存放在内存中就是78 56 34 12
。大端则和我们读写方式一致(从低到高地址的读写方式),同样的x存放顺序为12 34 56 78
.
堆与栈
首先看一下堆与栈的结构:
堆与栈其实是在同一段地址空间的,不同的是栈是往下增长,堆是往上增长;所以说应该写道堆的数据因为太多写到栈中是合理的(虽然不应该这么做),但是因为栈是程序自动分配、堆是自己分配的,所以非常有可能会发生堆栈冲突!
在C++中,内存分成5个区,分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
栈:就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
堆:就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
自由存储区:就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。
对于堆区、栈区和静态存储区它们之间最大的不同在于,栈的生命周期很短暂。但是堆区和静态存储区的生命周期相当于与程序的生命同时存在(如果不在程序运行中间将堆内存delete的话),我们将这种变量或数据成为全局变量或数据。但是,对于堆区的内存空间使用更加灵活,因为它允许你在不需要它的时候,随时将它释放掉,而静态存储区将一直存在于程序的整个生命周期中。
联系
我们分别写一个非常简单的程序来看一下0x12345678
与a
这两个是怎么存储在内存中的:
栈中:
堆中:
静态存储区中:
但是自己一直觉得有问题的地方是这样的,我们都知道程序在“中断”跳转的时候是会在栈中保存程序的上下文的(返回地址、变量.....);所以我认为我们在保存结构体的时候,虽然结构体也是在编译的时候视为数据类型,然后分配在栈中。但是结构体中的成员变量的存放应该也和自身的类型有关,所以如果他们也是局部变量的时候应该也分配在栈中,其地址顺序也应该符合栈的存放方式,但是看下图:
顺序不对啊?其实这里犯了一个很大的错误,就是并没有意识到成员变量此时并不是所谓的"局部变量"了!!而是非静态成员变量,我们可以通过类的内存分配看到如下结构:
这里的x与c都是非静态成员变量,是分配在堆中的,所以可以解释了。
而成员变量和局部变量是有区别的:
a定义的位置不同
成员变量:定义于类中,作用于整个类
局部变量:定义于方法或者语句中,作用于该方法或者该语句。
b内存中出现的时间和位置不同
成员变量:当对象创建时,出现在堆内存当中。
局部变量:所属区域被运算时,出现在栈内存当中。
c生命周期不同
成员变量:随着对象的出现而出现,随着对象的消失而消失。
局部变量:随着所属区域运算结束,它就被释放。
d初始化值不同
成员变量:成员变量因为在堆内存中,所以它有默认初始值。
局部变量:没有默认的初始值。
那么如果我们在class中定义一个函数,函数中用了局部变量,那么这个类中非静态成员函数的局部变量是符合我们说的数据存放规则的么?答案是:是的!
这里引用一下总结的静态成员变量与非静态成员变量的区别:
1、从保存位置:
a) 静态成员变量: 方法区的静态区域
b) 非静态成员变量: 堆内存中的对象空间里面
2、从书写格式上看:
a) 静态成员变量: 在数据类型前面多了一个static修饰
b) 非静态成员变量: 没有static修饰
3、从生命周期上看:
a) 静态成员变量: 在类加载的时候,类加载完成,就分配完空间;直到类被卸载时空间被回收
b) 非静态成员变量: 创建对象的时候分配空间; 对象变为垃圾空间被回收的时候被销毁
4、从使用方法上看:
a) 静态成员变量: 直接通过类名使用
b) 非静态成员变量: 必须通过对象使用
5、从修改后的影响范围上看:
a) 静态成员变量: 对该类的所有对象都有影响;
b) 非静态成员变量: 只对一个对象有影响
结语
其实写完以后发现其实并不需要纠结的,就是根据变量类型按照堆或栈的顺序存储,然后再根据数据大小分成字节,再按字节序存储就行了。