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语言源代码里面某些程序直接这样写的。

山寨机(机器配置比较低)上写程序,需要好好考虑内存的使用,比如嵌入式系统中写程序。

=============================================================================

posted @ 2018-01-15 19:10  黑泽君  阅读(573)  评论(0编辑  收藏  举报