c指针和内存之内存四区

c内存四区介绍,总结、摘录自王保明《C提高讲义》相关资料,待细化

内存四区的建立流程

  1. 操作系统把物理硬盘代码load到存
  2. 操作系统把c代码分成四个区
  3. 操作系统找到main函数入口执行
    内存四区的建立流程

各区元素分析

各区元素分析

函数调用模型

各区元素分析
各区元素分析-结束

常量区测试

如果两个常量的值一样,C编译器会自动优化,将两个变量的地址做成一样的。全局区和常量区通常一起考虑。

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
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char* getStr1()
{
char* p = "abcd";
return p;
}

char* getStr2()
{
char* p = "abcd";
return p;
}

char* getStr3()
{
char* p = "abcde";
return p;
}

int main()
{
char* p1 = getStr1();
char* p2 = getStr2();
char* p3 = getStr3();
printf("p1: %s, p1 address: %d \n", p1 ,p1);
printf("p2: %s, p2 address: %d \n", p2, p2);
printf("p3: %s, p3 address: %d \n", p3, p3);
system("pause");
}

运行结果

c++编译器检查更严格,如果将文件后缀改为.cpp,将会报错:

编译错误

举例

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
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char* getStr1()
{
char* p = "abcd";
return p;
}

char* getStr2()
{
char* p = "abcd";
return p;
}

char* getStr3()
{
char* p = "abcde";
return p;
}

char* getStrError()
{
char buf[100] = { 0 };
strcpy_s(buf, 100,"abcde");
return buf;
}

int main()
{
//此处画图就直观的感受到错误
//char* pError = getStrError();
//printf("pError: %s \n", pError);

int n1 = 0;
int n2 = 0;
printf("n1 address: %d \n", &n1);
printf("n2: address: %d \n", &n2);
char c[100] = { 'a', 'b', 'c'};
printf("c[0]: address: %d \n", &c[0]);
printf("c[1]: address: %d \n", &c[1]);
char* p1 = getStr1();
char* p2 = getStr2();
char* p3 = getStr3();
char* p4 = (char*)malloc(100);
printf("p1: %s, p1 value: %d, p1 address: %d \n", p1 , p1, &p1);
printf("p2: %s, p2 value: %d, p2 address: %d \n", p2, p2, &p2);
printf("p3: %s, p3 value: %d, p3 address: %d \n", p3, p3, &p3);

char** p5 = (char**)malloc(3 * sizeof(char*));
char* p6 = (char*)malloc(4 * sizeof(char));

for (int i = 0; i < 3; i++)
{
p5[i] = (char*)malloc(100*sizeof(char));
printf("p5[%d] value: %d, address: %d \n", i, p5[i], &p5[i]);
}

char* p7 = (char*)malloc(4 * sizeof(char));

printf("p5 value: %d, p5 address: %d \n", p5, &p5);
printf("p6 value: %d, p6 address: %d \n", p6, &p6);
printf("p7 value: %d, p7 address: %d \n", p7, &p7);

//未执行free
system("pause");
}

运行结果如下:
运行结果

备份打印地址,方便后面改堆栈图

1
2
3
4
5
6
7
8
9
10
11
12
13
n1 address: 11533720
n2: address: 11533708
c[0]: address: 11533600
c[1]: address: 11533601
p1: abcd, p1 value: 6454064, p1 address: 11533588
p2: abcd, p2 value: 6454064, p2 address: 11533576
p3: abcde, p3 value: 6454072, p3 address: 11533564
p5[0] value: 16231568, address: 16215752
p5[1] value: 16231712, address: 16215756
p5[2] value: 16231856, address: 16215760
p5 value: 16215752, p5 address: 11533540
p6 value: 16215808, p6 address: 11533528
p7 value: 16232000, p7 address: 11533504

函数调用过程是个动态的,示例需要在中动态改变,不好展现,可参考函数调用模型脑补,这里只画main函数的内存四区图。

四区图

注意

  1. 变量本质是存空间的别名,不要将变量名写到栈中
  2. 指针也是一种变量,占有内存空间,用来保存内存地址。指向谁就把把谁的地址赋值给指针。
  3. 从打印地址可得,WIN10/VS2019平台下栈口是朝下的(栈向下增长),堆和全局区是朝上的(堆向上增长)(紫色箭头)
  4. 数组内存增长(内存存放)的方向和在栈中还是堆中是无关的,都是向上增长。(紫色箭头)
  5. 指针变量和他所指向的内存空间变量是两个不同的概念。
  6. 没有内存就没有指针,要分清是在主调用函数分配的内存还是在被调用函数分配的内存,是在堆上分配的内存还是在栈上分配的内存。
  7. 主调函数可把堆区、栈区、全局数据存地址传给被调用函数,被调用函数只能返回堆区、全局数据。

参考


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!