内存四区模型
内存由四大区域组成
分别是
- 堆区
- 栈区
- 代码区
- 数据区
这里可以参考内存分区模型 -> 内存分区模型
栈区
对于栈区来讲,它是先进后出的。
程序使用前,编译器会根据代码来分配合适的栈空间,程序中的局部变量,返回值,参数等等都会放在栈空间中,编译器也会在程序结束时进行回收,如果栈空间的内存不足,就会导致内存溢出 overflow。
但是栈空间是完全由编译器管理的。
堆区
堆区是程序员自己申请分配和管理的一块内存区域,要分配就要释放。例如一个两个节点的动态链表:
#include <iostream>
#include <cstdio>
#include <malloc.h>
using namespace std;
struct LinkedNode {
int data;
LinkedNode* next;
};
typedef LinkedNode Node;
int main() {
Node* p1 = NULL, * p2 = NULL;
p1 = (Node*)malloc(sizeof(Node));
p2 = (Node*)malloc(sizeof(Node));
//Consider p1 and p2 allocate success
p1->next = p2;
p2->next = NULL;
printf("%p\n%p", p1, p2);
return 0;
}
代码区
数据区
数据区中存放着静态变量,全局变量等。
例如下面这段代码
#include <iostream>
#include <cstdio>
using namespace std;
int g_var = 20;
int main() {
int a = 1;
static int b = 2;
const int c = 10;
printf("int a = %d, \tadd = %p\n", a,&a);
printf("const int c = %d, \tadd = %p\n", c, &c);
printf("static int b = %d, \tadd = %p\n", b,&b);
printf("globe int g_var = %d,\tadd = %p\n", g_var,&g_var);
return 0;
}
他的输出结果如下:
int a = 1, add = 00F8F82C
const int c = 10, add = 00F8F820
static int b = 2, add = 0076C008
globe int g_var = 20, add = 0076C004
我们发现变量a和变量c是连续的
但是静态变量和局部变量和前者并不连续,因为他们储存在数据区
而数据区分为
Data Segment (数据区)
存放已初始化的全局和静态变量, 常量数据(如字符串常量)。
BSS(Block started by symbol)
存放未初始化的全局和静态变量。(默认设为0)
内存对齐
内存占位
首先以整型和字符型举例
我们通过如下代码知晓了两个数据类型的大小:
int main() {
int _intVar;
char _charVar;
printf("int takes \t%d\n", sizeof(_intVar));
printf("char takes \t%d\n", sizeof(_charVar));
return 0;
}
int takes 4
char takes 1
内存对齐
那么有两个结构体定义如下:
struct MemoryAligned {
int _intVar01;
char _charVar01;
char _charVar02;
char _charVar03;
char _charVar04;
};
struct MemoryNotAligned {
char _charVar01;
int _intVar01;
char _charVar02;
};
在第一个结构体中,定义了5个变量,第二个结构体中只定义了3个,那么按照内存分配来说,第一个占据的内存空间应该比第二个大。
memory aligned takes 8
memory not aligned takes 12
但结果却是第二个内存占据了比第一个内存还小的空间。
之所以出现这种现象是因为内存没有对齐
下面我们看这个对齐的示意图:
内存操作
内存的开辟
通过new或者其他内存开辟函数和方法来开辟内存,内存大小可以指定。
例如这个函数可以指定特定的类型和空间大小。
/*被注释掉的部分是C语言风格的,但是C语言并不支持模板和decltype表达式,所以不建议使用*/
#include <iostream>
#include <malloc.h>
using std::cout;
using std::endl;
template <typename T>
T* AllocateBlock(int number){
/*
T *p = NULL;
p = (T*)malloc(sizeof(T)*number);
return p;
*/
T *pArray = new T(number);
return pArray;
}
int main(){
int test_type;
decltype(test_type) *p = NULL;
p = AllocateBlock<decltype(test_type)>(10);//开辟10个test_type类型的内存空间
//free(p);
delete p;
return 0;
}
内存的精确写入
如果要在一个块中写入特定的值,并将其通用化。可以使用malloc函数,这是一个底层函数。
/**
2021/2/8
Phil616@163.com
*/
#include <iostream>
#include <typeinfo>
#include <malloc.h>
using std::cout;
using std::endl;
//提供了在特定内存块写入特定值的函数
template<typename T>
T *TestBlock(T data){
T *p = (T*)malloc(sizeof(T));
cout << "size of " << typeid(T).name() << " is " << sizeof(T) << endl;
*p = data;
cout << "block storge " << (int)*p << " at " << &p << endl;
return p;
}
int main(){
char test_type;
decltype(test_type)*p =NULL;//指定类型
p = TestBlock<decltype(test_type)>(0b01000100);//写值
free(p);
return 0;
}
以上函数在调用时的参数是二进制,在内存块中申请了一个字节,这个字节是八个位,八个位写的值非常精确。
内存排布
void showMemoryBytes(void *startADD,int len) {
const char *header = "|Address |0x|0b |\n|--------|--|---------|\n";
char HexList[16][5] = {
"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"
};
unsigned char *pStart = (unsigned char *)startADD;
printf(header);
for (int i = 0; i < len; i++)
{
printf("|%p|%.2x|%s %s|\n", pStart, *pStart,HexList[*pStart/16], HexList[*pStart%16]);
pStart++;
}
}
调用上面的函数可以查看内存的排布情况