zoukankan      html  css  js  c++  java
  • 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
    ", 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
    ", 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
    ", 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
    ", &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
    ", 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
    ", 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
    ", 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
    ", *p); //10
     72     free(p);
     73 
     74     //char *p1 = test2();       //编译时同样出现警告,因为test执行完后内部的自动变量a已经不在内存了,所以p指向了一个无效的地址,也即变成了野指针了。
     75     //printf("%s
    ", p1);       //忽略警告后,执行输出结果不可知。
     76     //free(p1);
     77 
     78     char *p2 = test3();
     79     printf("%s
    ", 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
    ", p);  //hahahaha  即从角标为0的元素开始输出。
     91 
     92     p = test5(a);       //该句等价于 p = &a[5],即从数组a的角标为5的元素开始。
     93     printf("%s
    ", p);  //aha  即从数组a的角标为5的元素开始输出。
     94 
     95     p = test6();
     96     printf("%s
    ", p);  //ab
     97     free(p);
     98 
     99     p = test7();
    100     printf("%s
    ", 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
    ", str);    //hello
    40 
    41     char *str1 = test1();
    42     printf("%s
    ", str1);   //ello
    43 
    44     const char *str2 = test2();
    45     printf("%s
    ", str2);   //hello
    46 
    47     const char *str3 = test3();
    48     printf("%s
    ", str3);   //hello world
    49 
    50     const char *str4 = test4();
    51     printf("%s
    ", str4);   //haha     函数定义的地址和返回的地址类型不符!
    52 
    53     char *str4 = test4();
    54     str4[0] = 'a';
    55     printf("%s
    ", 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
    ", 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
    ", 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
    ", 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
    ", 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("在原有的基础上增加内存
    ");
    59     }
    60     else
    61     {
    62         printf("不是在原有的基础上增加内存
    ");
    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
    ", 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
    ", 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
    ", 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语言源代码里面某些程序直接这样写的。

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

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

  • 相关阅读:
    Nginx、PCRE和中文URL(UTF8编码)rewrite路径重写匹配问题
    Nginx 使用中文URL,中文目录路径
    再谈Nginx Rewrite, 中文URL和其它
    事务管理
    commons-dbcp连接池的使用
    JDBC操作简单实用了IOUtils
    JDBC进行处理大文件和批处理
    mysql日期函数(转)
    mysql约束(自己原先总结的有点不准)
    mysql笔记(前面自己写的不标准有些地方)
  • 原文地址:https://www.cnblogs.com/chenmingjun/p/8289447.html
Copyright © 2011-2022 走看看