c语言基础学习08_内存管理
=============================================================================
涉及到的知识点有:
一、内存管理、作用域、自动变量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语言源代码里面某些程序直接这样写的。
在山寨机(机器配置比较低)上写程序,需要好好考虑内存的使用,比如嵌入式系统中写程序。
=============================================================================
【转载文章务必保留出处和署名,谢谢!】