============================================================================= 涉及到的知识点有: 一、内存管理、作用域、自动变量auto、寄存器变量register、代码块作用域内的静态变量、代码块作用域外的静态变量。
二、内存布局、代码区 code、静态区 static、栈区 stack、堆区 heap。
三、堆的分配和释放、c语言几个使用堆内存的库函数:malloc函数、free函数、calloc函数、realloc函数、 函数的返回值为指针类型01_(即函数的返回值是一个地址)、函数的返回值为指针类型02_、 堆的使用例子:通过堆空间实现动态大小变化的字符数组、函数calloc 和 函数realloc 的使用案例、 通过函数形参为一级指针时,在函数内部分配堆内存的错误案例、通过函数形参为二级指针时,在函数内部分配堆内存的正确案例。 ============================================================================= ============================================================================= 一、内存管理
实际上内存管理是通过指针来管理的。要想写出高质量的代码,必须要了解计算机的内存。
1、作用域
一个c语言变量的作用域可以是代码块作用域、函数作用域、文件作用域。
代码块:是指大括号{...}之间的一段代码。
同一个作用域不能有同名变量,但不同作用域变量名称可以相同。 打比方:同一个家里面的人的名字不能一样。
linux下示例代码如下:
1 #include <stdio.h>
2
3 int c = 2; //文件作用域,因为它属于这个文件本身,并不在任何一个函数里面。
4
5 int main()
6 {
7 int a = 0; //函数作用域。
8 {
9 int b = 1; //代码块作用域。
10 }
11
12 return 0;
13 }
14 --------------------------------------
15 //3个作用域的名字可以一样,不冲突的。因为它们的作用域不一样!
16
17 int a = 2; //文件作用域,因为它属于这个文件本身,并不在任何一个函数里面。
18
19 int main()
20 {
21 int a = 0; //函数作用域。
22 {
23 int a = 1; //代码块作用域。
24 }
25
26 return 0;
27 }
============================================================================= 2、自动变量auto
一般情况下,代码块内部定义的变量都是自动变量。当然也可以显示的使用auto关键字, 所有的自动变量的生命周期就是变量所属的大括号。
例如: auto signed int a = 0; //定义了一个自动变量。二者等价 int a = 0; ============================================================================= 3、寄存器变量register
通常变量在内存当中,如果能把变量放到cpu的寄存器里面,代码的执行效率会更高。
例如: register int a = 0; //定义了一个寄存器变量。 ============================================================================= 4、代码块作用域内的静态变量
静态变量是指在程序执行期间一直不改变的变量,一个代码块内部的静态变量只能被这个代码内部访问。
例如: static int i = 0; //定义了一个静态变量。 ----------------------------------------------------------------------------- linux下示例代码如下:
1 #include <stdio.h>
2
3 void test()
4 {
5 auto signed int a = 0; //等价于 int a = 0;
6
7 a++;
8 printf("a = %d\n", a);
9 }
10
11 int main()
12 {
13 register int a = 0; //寄存器变量。
14 static int b = 0; //静态变量。
15
16 int i;
17 for (i = 0; i < 10; i++)
18 {
19 test();
20 }
21
22 return 0;
23 }
24 输出的结果为:
25 a = 1
26 a = 1
27 a = 1
28 a = 1
29 a = 1
30 a = 1
31 a = 1
32 a = 1
33 a = 1
34 a = 1
----------------------------------------------------------------------------- 静态变量在程序刚加载进内存的时候就出现了,所以它和定义静态变量的大括号无关, 一直到程序结束的时候才从内存中消失,同时静态变量的值只初始化一次。
linux下示例代码如下:
1 #include <stdio.h>
2
3 void test()
4 {
5 static int a = 0; //等价于 int a = 0;
6
7 a++;
8 printf("a = %d\n", a);
9 }
10
11 int main()
12 {
13 register int a = 0; //寄存器变量。
14 static int b = 0; //静态变量。
15
16 int i;
17 for (i = 0; i < 10; i++)
18 {
19 test();
20 }
21
22 return 0;
23 }
24 输出的结果为:
25 a = 1
26 a = 2
27 a = 3
28 a = 4
29 a = 5
30 a = 6
31 a = 7
32 a = 8
33 a = 9
34 a = 10
============================================================================= 5、代码块作用域外的静态变量
代码块之外的静态变量在程序执行期间一直存在,但只能被定义这个变量的文件访问, 代码块之外的静态变量只能在定义这个变量的文件中使用,在其他文件中不能被访问。
因为全局变量的名字是不能相同的,这样会带来一个什么问题? 因为一个项目往往是多个人写的,每个人都定义自己的全局变量,最后代码合并时会出错。 但是static定义的全局变量在不同文件中的名字是可以相同的。 ============================================================================= 6、全局变量
全局变量的存储方式和静态变量相同,但可以被多个文件访问,定义在代码块之外的变量就是全局变量。
全局变量即使不在同一个文件里面,也不能重名。 -------------------------------------- linux下示例代码如下:
mem3.c文件内容如下:
1 #include <stdio.h>
2
3 extern int a; //声明了一个变量a。extern的意思是:外面的,外来的。
4
5 //void test(); //简便写法。
6 extern void test(); //声明了一个函数。更严谨的写法。意思是说:该函数是外部函数,在其他地方定义了。
7
8 int main()
9 {
10 test();
11 printf("a = %d\n", a );
12
13 return 0;
14 }
-------------------------------------- mem4.c文件内容如下:
1 int a = 1; //a是一个全局变量。
2
3 void test()
4 {
5 a++;
6 }
输出结果为:
root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/内存管理# gcc -o a mem3.c mem4.c
root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/内存管理# a
a = 2
root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/内存管理#
----------------------------------------------------------------------------- 静态全局变量只能在定义它的文件内部访问,对于文件外部其他的文件是不可以使用的(访问的)。 如果在代码块之外的一个变量或者函数,c语言默认都是全局的。除非写了个static就改变了它的类型了。 ============================================================================= ============================================================================= 二、内存布局
1、代码区 code
程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段, 这块内存是不可以在运行期间修改的。
代码区中所有的内容在程序加载到内存的时候就确定了,运行期间不可以修改,只可以执行。 ============================================================================= 2、静态区 static
静态区是程序加载到内存的时候就确定了,程序退出的时候从内存消失。
所有的全局变量和静态变量在程序运行期间都占用内存。
所有的全局变量以及程序中的静态变量都存储到静态区。 -------------------------------------- linux下示例代码如下:
1 #include <stdio.h>
2
3 int a = 0; //在静态区存储。
4 static int b = 1; //在静态区存储,与 int b = 1; 的地址是一样的。
5
6 int main()
7 {
8 static int c = 2; //在静态区存储。
9 auto int d = 3; //自动变量在栈中存储。
10 int e = 4; //自动变量在栈中存储。
11
12 printf("%p, %p, %p, %p, %p\n", &a, &b, &c, &d, &e); //0x60104c, 0x601040, 0x601044, 0x7ffe3fddd1c0, 0x7ffe3fddd1c4
13
14 return 0;
15 }
============================================================================= 3、栈区 stack
栈是一种先进后出的内存结构,所有的 自动变量、函数的形参、函数的返回值 都是由编译器自动放入栈中。
当一个自动变量超出其作用域时,会自动从栈中弹出。
不同的系统下栈的大小是不一样的,即使是相同的系统,栈的大小也是不一样的。一般来讲栈不会很大,单位是多少K字节。 windows系统下的程序在编译的时候就可以指定栈的大小,linux系统下栈的大小是可以通过环境变量来设置的。 ============================================================================= 4、堆区 heap
堆和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但是没有栈那样先进后出的顺序。
堆的使用较复杂些,堆内存空间的申请和释放需要我们手动通过代码来完成。
对是一个大容器,它的容量要远远大于栈,但是在c语言中,堆内存空间的申请和释放需要我们手动通过代码来完成。
============================================================================= ============================================================================= 三、堆的分配和释放
c语言几个使用堆内存的库函数,需要用到头文件 #include <stdlib.h>。 ============================================================================= 1、malloc函数
void *malloc(size_t size); malloc函数的功能是:在堆中分配指定大小的内存,单位是:字节。 函数返回值是:void *指针。(无类型指针) ============================================================================= 2、free函数 void free(void *ptr); free函数的功能是:负责在堆中释放有malloc分配的内存。 参数是:ptr为malloc返回堆中的内存地址。 ============================================================================= 3、calloc函数 void *calloc(size_t nmemb, size_t size); calloc函数与malloc函数的功能类似,都是负责在堆中分配内存。 malloc只负责分配,但不负责清理内存。 calloc分配内存的同时把内存清空(即置0)。
第一个参数是:所需分配内存的单元数量;第二个参数是:每隔内存单元堆的大小(单位:字节)。 ============================================================================= 4、realloc函数 void *realloc(void *ptr, size_t size); realloc函数的功能是:重新分配用malloc函数或calloc函数在堆中分配内存空间的大小。 第一个参数是:ptr为之前用malloc或calloc分配的堆内存地址,第二个参数是:重新分配内存的大小,单位:字节。 realloc函数成功则返回重新分配的堆内存地址,失败返回NULL。 若擦数ptr = NULL,那么realloc和malloc的功能一样了。
realloc也不会自动清理增加后的内存,也需要手动清理。
如果指定地址后面有连续的空间,那么realloc就会在已有地址的基础上增加内存。 如果指定的地址后面没有多余的空间,那么realloc会重新分配新的连续内存,把进内存的值拷贝到新的内存,并同时释放旧内存。 (这是realloc的智能之处) ----------------------------------------------------------------------------- linux下示例代码如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 int main()
6 {
7 //一个栈里面的自动指针变量s指向了一个堆的地址空间。
8 auto char *s;
9 s = malloc(10); //在堆中申请了(分配了)10个字节的空间,又因为返回值是void *,所以该句为在堆中申请了(分配了)10个char的空间。
10
11 strcpy(s, "abcd");
12 printf("%s\n", s); //abcd
13 free(s); //释放堆中的内存。不释放的话就会一直占着!
14
15 s = malloc(100); //因为s是自动指针变量,释放后可以重新使用,这个时候s又重新指向了一个新的堆地址空间。
16 free(s); //free(s);并不是把自动指针变量s释放了,而是释放了s所指向的那块堆内存空间。
17
18 //一个程序的栈大小是有限的,如果一个数组特别大,有可能会导致栈溢出,所以不要在栈里面定义太大的数组。
19 //int a[100000000]; //定义了一个数组,这个数组在内存的栈区里面。
20 //a[99999999] = 0; //程序编译没有问题,但是程序运行出现Segmentation fault(段错误)
21
22 printf("%lu\n", sizeof(int)); //4
23
24 //当一个数组特别大时,我们可以使用堆。
25 int *p = malloc(100000000 * sizeof(int));
26 p[99999999] = 0;
27 free(p);
28
29 return 0;
30 }
----------------------------------------------------------------------------- 什么时候在栈中使用一个数组呢?又什么时候在堆中使用一个数组呢?
1、如果使用一个特别大的数组,那么需要把数组放入堆中,而不是栈。
2、如果一个数组在定义的时候,大小不能确定,那么适合用堆,而不是栈。
3、如果malloc分配的内存忘记free,那么会发生内存泄漏,这个也是初学者最容易犯的错误。
malloc分配的空间里面的值是随机的,不会自动置0。
堆到底有多大呢?它取决于物理内存,取决于操作系统本身,并不取决于你的程序。如下代码:
//可以根据用户的输入在堆中分配大小不同的数组。 linux下示例代码如下:
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int main()
6 {
7 int i;
8 scanf("%d", &i);
9
10 int *p = malloc(i * sizeof(int));
11
12 int a;
13 for (a = 0; a < i; a++)
14 {
15 printf("%d\n", p[i]);
16 }
17
18 free(p);
19
20 return 0;
21 }
============================================================================= 函数的返回值为指针类型01_(即函数的返回值是一个地址)
linux下示例代码如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 int *test()
6 {
7 int a = 10; //a是auto,是局部变量。在栈里面。空间会自动释放。
8 return &a; //出现编译警告:warning: function returns address of local variable [-Wreturn-local-addr]
9 //警告:函数返回局部变量的地址[-Wreturn-local-addr]
10 }
11
12 int *test1()
13 {
14 int *p = malloc(1 * sizeof(int)); //在堆中申请了(分配了)4个字节的空间,也即一个int的空间。在堆里面。空间不会自动释放。
15 *p = 10;
16 return p;
17 }
18
19 char *test2()
20 {
21 char a[100] = "hello"; //a是auto,是局部变量。
22 return a; //同样出现编译警告:warning: function returns address of local variable [-Wreturn-local-addr]
23 //警告:函数返回局部变量的地址[-Wreturn-local-addr]
24 }
25
26 char *test3()
27 {
28 char *a = malloc(100); //在堆中申请了(分配了)100个字节的空间。也即100个char的空间。
29 strcpy(a, "hello");
30 return a;
31 }
32 //由test()和test2()可知:在函数内部不能直接返回一个auto类型变量的地址,因为auto类型变量的地址都是自动的,一旦该函数执行完后,这个地址就无效了。
33
34 //-----------------------------------------------------------------------------
35 char *test4(char *arg)
36 {
37 return arg;
38 }
39
40 char *test5(char *arg)
41 {
42 return &arg[5]; //返回下标为5的成员变量的地址。
43 }
44
45 char *test6()
46 {
47 char *p = malloc(100);
48 *p = 'a'; //等价于 p[0] = 'a';
49 *(p + 1) = 'b'; //等价于 p[1] = 'b';
50 *(p + 2) = '0'; //等价于 p[2] = '0'; 或 p[2] = 0; 或 *(p + 2) = 0;
51
52 return p;
53 }
54
55 char * test7()
56 {
57 char *p = malloc(100);
58 *p = 'a';
59 p++;
60 *p = 'b';
61 p++;
62 *p = 0;
63
64 return p;
65 }
66
67 int main01()
68 {
69 //int *p = test(); //编译时出现警告,因为test执行完后内部的自动变量a已经不在内存了,所以p指向了一个无效的地址,但是这块内存的内容还在。也即变成了野指针了。
70 int *p = test1(); //是通过堆内存分配函数进行内存分配的,函数test1执行完后,内存不会自动释放的。
71 printf("%d\n", *p); //10
72 free(p);
73
74 //char *p1 = test2(); //编译时同样出现警告,因为test执行完后内部的自动变量a已经不在内存了,所以p指向了一个无效的地址,也即变成了野指针了。
75 //printf("%s\n", p1); //忽略警告后,执行输出结果不可知。
76 //free(p1);
77
78 char *p2 = test3();
79 printf("%s\n", p2); //hello
80 free(p2);
81
82 return 0;
83 }
84
85 int main()
86 {
87 char a[100] = "hahahaha"; //定义了一个auto自动变量是数组变量,在栈里面。
88 char *p;
89 p = test4(a); //该句执行后:等价于 p = a;或 p = &a[0]; p指向的是栈里面的地址。
90 printf("%s\n", p); //hahahaha 即从角标为0的元素开始输出。
91
92 p = test5(a); //该句等价于 p = &a[5],即从数组a的角标为5的元素开始。
93 printf("%s\n", p); //aha 即从数组a的角标为5的元素开始输出。
94
95 p = test6();
96 printf("%s\n", p); //ab
97 free(p);
98
99 p = test7();
100 printf("%s\n", p); //编译没有问题,执行出现问题。
101 free(p);
102
103 return 0;
104 }
输出结果为:
root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/内存管理# gcc -o a mem8.c
root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/内存管理# a
hahahaha
aha
ab
*** Error in `a': free(): invalid pointer: 0x0000000000cc8422 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f517e1cb7e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f517e1d437a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f517e1d853c]
a[0x400916]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f517e174830]
a[0x400599]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:01 1050518 /root/1/01/内存管理/a
00600000-00601000 r--p 00000000 fd:01 1050518 /root/1/01/内存管理/a
00601000-00602000 rw-p 00001000 fd:01 1050518 /root/1/01/内存管理/a
00cc8000-00ce9000 rw-p 00000000 00:00 0 [heap]
7f5178000000-7f5178021000 rw-p 00000000 00:00 0
7f5178021000-7f517c000000 ---p 00000000 00:00 0
7f517df3e000-7f517df54000 r-xp 00000000 fd:01 1180169 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f517df54000-7f517e153000 ---p 00016000 fd:01 1180169 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f517e153000-7f517e154000 rw-p 00015000 fd:01 1180169 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f517e154000-7f517e314000 r-xp 00000000 fd:01 1185266 /lib/x86_64-linux-gnu/libc-2.23.so
7f517e314000-7f517e514000 ---p 001c0000 fd:01 1185266 /lib/x86_64-linux-gnu/libc-2.23.so
7f517e514000-7f517e518000 r--p 001c0000 fd:01 1185266 /lib/x86_64-linux-gnu/libc-2.23.so
7f517e518000-7f517e51a000 rw-p 001c4000 fd:01 1185266 /lib/x86_64-linux-gnu/libc-2.23.so
7f517e51a000-7f517e51e000 rw-p 00000000 00:00 0
7f517e51e000-7f517e544000 r-xp 00000000 fd:01 1185244 /lib/x86_64-linux-gnu/ld-2.23.so
7f517e737000-7f517e73a000 rw-p 00000000 00:00 0
7f517e740000-7f517e743000 rw-p 00000000 00:00 0
7f517e743000-7f517e744000 r--p 00025000 fd:01 1185244 /lib/x86_64-linux-gnu/ld-2.23.so
7f517e744000-7f517e745000 rw-p 00026000 fd:01 1185244 /lib/x86_64-linux-gnu/ld-2.23.so
7f517e745000-7f517e746000 rw-p 00000000 00:00 0
7ffce8530000-7ffce8551000 rw-p 00000000 00:00 0 [stack]
7ffce85c5000-7ffce85c7000 r--p 00000000 00:00 0 [vvar]
7ffce85c7000-7ffce85c9000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Aborted (中止)
root@iZ2zeeailqvwws5dcuivdbZ:~/1/01/内存管理#
错诶原因如下图:指针位移后free的问题说明
============================================================================= 函数的返回值为指针类型02_
linux下示例代码如下:
1 #include <stdio.h>
2
3 char *test()
4 {
5 static char a[100] = "hello";
6
7 return a;
8 }
9
10 char *test1()
11 {
12 static char a[100] = "hello";
13 char *p = a;
14 p++;
15
16 return p;
17 }
18
19 const char *test2()
20 {
21 const char *s = "hello"; //该意思是将s指向一个常量的地址,常量在程序运行期间是一直有效的。
22
23 return s;
24 }
25
26 const char *test3() //test2() 和 test3() 是一样的!
27 {
28 return "hello world";
29 }
30
31 char *test4()
32 {
33 return "haha"; //返回的是常量地址。而函数定义的却是变量地址。类型不符。
34 }
35
36 int main()
37 {
38 char *str = test();
39 printf("%s\n", str); //hello
40
41 char *str1 = test1();
42 printf("%s\n", str1); //ello
43
44 const char *str2 = test2();
45 printf("%s\n", str2); //hello
46
47 const char *str3 = test3();
48 printf("%s\n", str3); //hello world
49
50 const char *str4 = test4();
51 printf("%s\n", str4); //haha 函数定义的地址和返回的地址类型不符!
52
53 char *str4 = test4();
54 str4[0] = 'a';
55 printf("%s\n", str4); //编译没有问题,但执行出现段错误!因为:常量区和静态区类似,程序运行期间有效,但常量区是只读的,不能修改。
56
57 return 0;
58 }
============================================================================= 堆的使用例子:通过堆空间实现动态大小变化的字符数组
linux下示例代码如下:
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int main01()
6 {
7 char a[10] = "hello"; //此时的栈不够用了或者栈用的很不灵活了。
8 char b[10] = "haha";
9
10 strcat(a, b);
11 printf("%s\n", a); //用strcat的时候要注意,第一个字符串一定要有足够的空间容纳第二个字符串。
12
13 return 0;
14 }
15
16 int main()
17 {
18 char a[] = "hello";
19 char b[] = "hahahahahahahahahahaha";
20 char *p = malloc(strlen(a) + strlen(b) + 1);
21
22 strcpy(p, a);
23 strcat(p, b);
24 printf("%s\n", p); //hellohahahahahahahahahahaha
25 free(p);
26
27 return 0;
28 }
============================================================================= 函数calloc 和 函数realloc 的使用案例:
linux下示例代码如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 //现在用malloc或者calloc已经分配了10个int,如果想扩大或者缩小这块内存,怎么办?用realloc。
6 //注意:用realloc增加的空间也不会自动清0。
7 int main01()
8 {
9 char *s1 = calloc(10, sizeof(char)); //在堆中分配了10个char空间。
10 char *s2 = calloc(10, sizeof(char));
11
12 strcpy(s1, "123456789");
13 strcpy(s2, "abcdef");
14
15 s1 = realloc(s1, strlen(s1) + strlen(s2) + 1); //根据s1和s2的实际长度扩充s1的大小。
16 strcat(s1, s2);
17
18 printf("%s\n", s1); //123456789abcdef
19
20 free(s1);
21 free(s2);
22
23 return 0;
24 }
25
26 //不用realloc函数来实现扩大或者缩小内存。
27 int main02()
28 {
29 char *s1 = calloc(10, sizeof(char)); //在堆中分配了10个char空间。
30 char *s2 = calloc(10, sizeof(char));
31
32 strcpy(s1, "123456789");
33 strcpy(s2, "abcdef");
34
35 char *tmp = malloc(strlen(s1) + strlen(s2) + 1);
36
37 strcpy(tmp, s1);
38 free(s1);
39 strcat(tmp, s2);
40 free(s2);
41
42 s1 = tmp;
43 free(s1);
44 printf("%s\n", s1); //123456789abcdef
45
46 return 0;
47 }
48
49 //malloc的智能体现:如果指定的地址后面有连续的空间,那么就会在已有的地址的基础上增加内存,
50 //如果指定的地址后面没有空间,那么realloc会重新分配新的连续内存,把旧内存的值拷贝到新内容,同时释放旧内存。
51 int main()
52 {
53 char *s1 = malloc(100);
54 char *p = realloc(s1, 5000000);
55
56 if (s1 == p)
57 {
58 printf("在原有的基础上增加内存\n");
59 }
60 else
61 {
62 printf("不是在原有的基础上增加内存\n");
63 }
64 free(p);
65
66 return 0;
67 }
realloc的智能体现如下图所示:
============================================================================= 通过函数形参为一级指针时,在函数内部分配堆内存的错误案例
linux下示例代码如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 void test(char *s)
6 {
7 strcpy(s, "hello");
8 }
9
10 void test1(char *s)
11 {
12 s = calloc(10, 1);
13 strcpy(s, "hello");
14 }
15
16 int main01()
17 {
18 char *p = calloc(10, 1); //堆中分配了10个char
19 test(p);
20
21 printf("%s\n", p); //hello
22 free(p);
23
24 return 0;
25 }
26
27 int main()
28 {
29 char *p = NULL;
30 test1(p);
31
32 printf("%s\n", p); //编译没有问题,但执行出现Segmentation fault (core dumped)(段错误)。
33 free(p);
34
35 return 0;
36 }
通过函数形参为一级指针时,在函数内部分配堆内存的错误案例的图解如下图所示:
============================================================================= 通过函数形参为二级指针时,在函数内部分配堆内存的正确案例
linux下示例代码如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 void test(char **s)
6 {
7 *s = calloc(10, 1);
8 strcpy(*s, "hello");
9 }
10
11 int main()
12 {
13 char *p = NULL;
14 test(&p);
15
16 printf("%s\n", p);
17 free(0);
18
19 return 0;
20 }
通过函数形参为二级指针时,在函数内部分配堆内存的正确案例的图解如下图所示:
============================================================================= windows系统分配内存的最小单位说明
vs2017下示例代码如下:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main()
5 {
6 while (1)
7 {
8 char *s = malloc(1024);
9 getchar(); //这个小函数的作用是:让程序执行到这里暂停一下。
10 }
11 return 0;
12 }
在windows系统下 任务管理器/详细信息 下查看内存变化:
304K 308K 312K 316K ......
得出结论: windows系统的每次堆变化是4K字节。 如果你需要1K的空间,操作系统会给4K; 如果你需要5K的空间,操作系统会给8K。 4K就是windows内存的最小页。内存是按照页来区分的。不是按照字节来区分的,不同的操作系统页的大小是不同的。 页的优点是:效率提升;缺点是:浪费了一些内存。
char *s = malloc(4 * 1024); //我们会发现:有些c语言源代码里面某些程序直接这样写的。
在山寨机(机器配置比较低)上写程序,需要好好考虑内存的使用,比如嵌入式系统中写程序。
=============================================================================