C 内存管理
AUTOGEN e082a75e863d49dbbc155f11e49ea778
C 内存管理
1. 内存四区
内存四区是指计算机内存在运行程序时划分的四个主要区域,用于存储不同类型的数据。这些区域包括:
- 代码区(Code Segment):也称为文本区,用于存储程序的执行代码。在程序运行之前,代码区的内容就已经被确定并且不能被修改。
数据区(Data Segment):数据区分为两个部分:
- 静态数据区(Static Data Segment):用于存储全局变量和静态变量。这些变量在程序的整个执行过程中都存在,并且其内存空间在编译时就已经分配好。
- 堆区(Heap Segment):用于存储动态分配的内存,例如通过new或malloc函数在运行时申请的内存。堆区的内存分配和释放由程序员手动控制。
- 栈区(Stack Segment):用于存储程序的局部变量和函数调用时的临时数据。栈区的内存分配和释放是自动进行的,由编译器根据函数的调用和返回自动管理。
- 常量区(Constant Segment):用于存储常量数据,例如字符串常量。常量区的内容在程序运行期间是不可修改的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# include <stdio.h>
# include <stdlib.h>
int G_var_1 = 1;
int G_var_2 = 2;
int G_var_3 = 3;
static int S_var_1 = 11;
static int S_var_2 = 12;
static int S_var_3 = 13;
const int C_var_1 = 101;
const int C_var_2 = 102;
int* create_a_heap(){
return (int*)malloc(sizeof(int));
}
void printmem(unsigned char *start, int len)
{
printf("a prefix at %p is %.2x\n",--start);
++start;
while(len--)
printf("%p -> %.2x \n",start,*(start++));
}
int main(){
printf("global variable 1 at %p is %d\n",&G_var_1,G_var_1);
printf("global variable 2 at %p is %d\n",&G_var_2,G_var_2);
printf("global variable 3 at %p is %d\n",&G_var_3,G_var_3);
printf("--------------\n");
printmem((unsigned char *)&G_var_1,3*4);
printf("static variable 1 at %p is %d\n",&S_var_1,S_var_1);
printf("static variable 2 at %p is %d\n",&S_var_2,S_var_2);
printf("static variable 3 at %p is %d\n",&S_var_3,S_var_3);
printf("--------------\n");
printmem((unsigned char *)&S_var_1,3*4);
printf("constant variable 1 at %p is %d\n",&C_var_1,C_var_1);
printf("constant variable 2 at %p is %d\n",&C_var_2,C_var_2);
printmem((unsigned char *)&C_var_1,2*4);
int * h = create_a_heap();
*h = 123;
printf("heap variable h at %p is %d\n",h,*h);
printmem((unsigned char *)h,8);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
global variable 1 at 00402000 is 1
global variable 2 at 00402004 is 2
global variable 3 at 00402008 is 3
--------------
a prefix at 00401FFF is 60ffcc
00402001 -> 01
00402002 -> 00
00402003 -> 00
00402004 -> 00
00402005 -> 02
00402006 -> 00
00402007 -> 00
00402008 -> 00
00402009 -> 03
0040200A -> 00
0040200B -> 00
0040200C -> 00
static variable 1 at 0040200C is 11
static variable 2 at 00402010 is 12
static variable 3 at 00402014 is 13
--------------
a prefix at 0040200B is 60ffcc
0040200D -> 0b
0040200E -> 00
0040200F -> 00
00402010 -> 00
00402011 -> 0c
00402012 -> 00
00402013 -> 00
00402014 -> 00
00402015 -> 0d
00402016 -> 00
00402017 -> 00
00402018 -> 00
constant variable 1 at 00403000 is 101
constant variable 2 at 00403004 is 102
a prefix at 00402FFF is 60ffcc
00403001 -> 65
00403002 -> 00
00403003 -> 00
00403004 -> 00
00403005 -> 66
00403006 -> 00
00403007 -> 00
00403008 -> 00
heap variable h at 00E01518 is 123
a prefix at 00E01517 is 60ffcc
00E01519 -> 7b
00E0151A -> 00
00E0151B -> 00
00E0151C -> 00
00E0151D -> c0
00E0151E -> 00
00E0151F -> e0
00E01520 -> 00
栈区
对于栈区来讲,它是先进后出的。
程序使用前,编译器会根据代码来分配合适的栈空间,程序中的局部变量,返回值,参数等等都会放在栈空间中,编译器也会在程序结束时进行回收,如果栈空间的内存不足,就会导致内存溢出 overflow。
但是栈空间是完全由编译器管理的。
堆区
堆区是程序员自己申请分配和管理的一块内存区域,要分配就要释放。例如一个两个节点的动态链表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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;
}
代码区
代码区涉及汇编,暂不讨论
数据区
数据区中存放着静态变量,全局变量等。
例如下面这段代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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;
}
1
2
3
4
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)
2. 内存对齐
内存占位
首先以整型和字符型举例
我们通过如下代码知晓了两个数据类型的大小:
1
2
3
4
5
6
7
int main() {
int _intVar;
char _charVar;
printf("int takes \t%d\n", sizeof(_intVar));
printf("char takes \t%d\n", sizeof(_charVar));
return 0;
}
1
2
int takes 4
char takes 1
内存对齐
1
2
3
4
5
6
7
8
9
10
11
12
struct MemoryAligned {
int _intVar01;
char _charVar01;
char _charVar02;
char _charVar03;
char _charVar04;
};
struct MemoryNotAligned {
char _charVar01;
int _intVar01;
char _charVar02;
};
在第一个结构体中,定义了5个变量,第二个结构体中只定义了3个,那么按照内存分配来说,第一个占据的内存空间应该比第二个大。
1
2
memory aligned takes 8
memory not aligned takes 12
但结果却是第二个内存占据了比第一个内存还小的空间。
之所以出现这种现象是因为内存没有对齐
下面我们看这个对齐的示意图:
3. 内存操作
内存开辟
通过new或者其他内存开辟函数和方法来开辟内存,内存大小可以指定。
例如这个函数可以指定特定的类型和空间大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*被注释掉的部分是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函数,这是一个底层函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
2021/2/8
[email protected]
*/
#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;
}
以上函数在调用时的参数是二进制,在内存块中申请了一个字节,这个字节是八个位,八个位写的值非常精确。
内存排布
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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++;
}
}
调用上面的函数可以查看内存的排布情况