• c语言基础学习08_关于内存管理的复习


    =============================================================================
    对于c语言来讲,内存管理是一个很重要的内容,它与指针是息息相关的,因为内存的管理都是通过指针来实现的。
    -----------------------------------------------------------------------------
    如果一个变量,它处在所有的代码块之外,那么它的生命周期就是和整个程序是一起的,程序启动的时候它就出现了,程序退出时,它才终止。
    如果一个变量,它处在代码块之内,那么这个代码块执行的时候它才出现,代码块执行完成后,它才消失。
    -----------------------------------------------------------------------------
    auto int i = 0;
    auto变量(自动变量)是在内存的栈里面,它是一个临时的变量,只有执行代码块的时候,它才会入栈,代码块执行完后,它才出栈。

    static int i = 0;
    static变量(静态变量)是在内存的静态区里面,整个程序运行期间,该变量都存在,而且静态变量只被初始化一次。

    例如:

    int i;
    for (i = 0; i < 5; i++)
    {    
        static int a = 10//定义了一个静态变量。
        a++;
        printf("%d
    ", a);
    }
    输出结果为:
    11
    12
    13
    14
    15
    --------------------------------------
    int i;
    for (i = 0; i < 5; i++)
    {    
        auto int a = 10//定义了一个自动变量。
        a++;
        printf("%d
    ", a);
    }
    输出结果为:
    11
    11
    11
    11
    11

    -----------------------------------------------------------------------------
    在代码块之外的变量都是全局变量,那么如果加了static后,依然是全局变量,但是此时变量的作用域局限在定义这个变量的文件内部。
    它其实还是放在静态区的,只是外部不能访问而已。

    同时函数前面也可以加一个static,如下所示:

    void test()    //没有static,默认该函数是全局的。
    {
      ;
    }

    static void test1()   //这个函数只能在定义这个函数的文件内被调用。
    {
      ;
    }
    注意:函数前面加static和本身的静态区没有任何关系,因为所有的函数都放在代码区,而静态区里面放的只是变量而已,不会放函数本身。

    即:static放在函数的不同位置对于c语言来讲它的意义是不一样!
    -----------------------------------------------------------------------------
    extern int a;         //这句话的意思是:a已经定义过了,这里只是声明。

    extern void test();  //这句话的意思是:函数test已经定义过了,这里只是声明。
    void test();          //对于函数来说,没有extern和 有extern 对于c语言是一样的。(c语言里面一个不太好的地方)

    extern int a;        //这句话的意思是:明确的声明了一个变量,一定不是表示定义一个变量。
    int a;              //这句话的意思是:如果这个变量已经定义过了,这里就代表声明;如果这个变量没有定义过,这里就代表定义。
                    //即:不能确定它是定义还是声明。也即:出现了二义性,比较含糊。
    -----------------------------------------------------------------------------

    在一个程序加载进内存的时候,操作系统会把不同类型的数据加载进不同的区域里面,例如:

    代码区:可执行代码加载进代码区;比如:所有的函数。

    静态区:所有的静态变量和全局变量都加载进静态区。实际上静态区是一个综合区,它会分很多子区,其中很多常量也是在静态区另外一个区里面放的。
      常量和普通静态变量有什么区别呢?
      不同点:常量也是在程序运行当中一直存在的,但是常量是只读的,普通的静态变量是可读可写的。
      相同点:他们的生命周期是一样的,整个程序运行的时候他们会出现在内存里面,整个程序执行完成以后他们才从内存里面消失。

    栈区:栈是一种先进后出的内存结构,所有的 自动变量、函数的形参、函数的返回值 都是由编译器自动放入内存的栈中。
      当一个自动变量超出其作用域时,会自动从栈中弹出。
      栈区特点是:函数调用时栈出现,函数结束时栈消失。

    堆区:堆和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。
      堆是一个大容器,它的容量要远远大于栈,但是在c语言中,堆内存空间的申请和释放需要手动通过代码来完成。
    -----------------------------------------------------------------------------
    c、c++会用到堆和栈,但是需要去手动维护。
    c#、java也会用到堆和栈,但是不需要去手动维护。因而付出的代价是:不能够选择你到底使用的是堆还是栈呢。

    作为一名使用者,使用c#、java语言,它们已经给你规定好了,有很多类,有很多对象,你尽管拿来用,但是它们这个类或者对象在哪里,我们不知道。
    我们也不需要知道,它们也不让你知道,我们就算知道也没用,因为我们也管不了,也处理不了。但是对于c语言,我们可以任意去控制这个变量是出现在栈还是堆里面。

    而且c语言还比较简单,因为它所有的都是变量。
    而c++和java还有对象,在c++里面可以指定你的对象在栈里面还是在堆里面,即可以选择效率最高的方法来使用对象,而在java里面所有的对象都是出现在堆里面的。

    这就是c和c++语言的魅力所在,以及它们做操作系统的原因之一,因为它们可以自由的控制内存中的每一个字节。
    -----------------------------------------------------------------------------
    用递归代码实现栈的先进后出效果(递归的过程是典型的先入栈后出栈过程)

    linux下示例代码如下:

     1 #include <stdio.h>
     2 
     3 //用递归代码实现栈的先进后出效果(递归的过程是典型的先入栈后出栈过程)
     4 
     5 void test(int n)
     6 {
     7     printf("%p, n = %d
    ", &n, n);  //把代码放到递归的前面,叫做先序递归。
     8     if (n < 3)
     9     {   
    10         test(n + 1); 
    11     }   
    12     printf("%p, n = %d
    ", &n, n);   //把代码放到递归的前面,叫做先序递归。
    13 }
    14 
    15 int main()
    16 {
    17     test(0);
    18 
    19     return 0;
    20 }
    21 输出结果为:
    22 0x7ffe5e720b0c, n = 0    第一个入栈
    23 0x7ffe5e720aec, n = 1
    24 0x7ffe5e720acc, n = 2
    25 0x7ffe5e720aac, n = 3
    26 0x7ffe5e720aac, n = 3
    27 0x7ffe5e720acc, n = 2
    28 0x7ffe5e720aec, n = 1
    29 0x7ffe5e720b0c, n = 0    最后一个出栈
    test(0)
    { 
        printf("%p, n = %d
    ", &n, n);    //0x7ffe5e720b0c, n = 0
        test(1)
        {
            printf("%p, n = %d
    ", &n, n);    //0x7ffe5e720aec, n = 1
            test(2)
            {
                printf("%p, n = %d
    ", &n, n);     //0x7ffe5e720acc, n = 2
                test(3)
                {
                    printf("%p, n = %d
    ", &n, n);    //0x7ffe5e720aac, n = 3
                    4 < 3;不符合if的判断条件,推迟if判断语句,则继续执行剩余代码:
                    printf("%p, n = %d
    ", &n, n);     //0x7ffe5e720aac, n = 3
                }
                printf("%p, n = %d
    ", &n, n);     //0x7ffe5e720acc, n = 2
            }
            printf("%p, n = %d
    ", &n, n);     //0x7ffe5e720aec, n = 1
        }
        printf("%p, n = %d
    ", &n, n);    //0x7ffe5e720b0c, n = 0
    }

    -----------------------------------------------------------------------------
    如果程序中申请了堆内存,但忘记了free,如果程序退出的时候操作系统会统一进行回收;但如果程序一直不退出,那么这块内存就会一直被占用,
    有时更可气的是,你不但不退出程序,而且还在不停的申请新的内存,也不free,最后操作系统的内存被你“吃光了”,导致内存泄漏!
    -----------------------------------------------------------------------------
    例如:
    int *p = malloc(200);
    p = realloc(p, 400);   //在p的基础上,将堆内存扩展到400个字节。
    p = realloc(p, 100);   //在p的基础上,将堆内存缩小到100个字节。
    int *p1 = realloc(NULL,100);   //如果realloc的第一个参数是NULL,那么realloc的作用和malloc是一样的。
    -----------------------------------------------------------------------------

    1、

     1 int *test()    //错误的代码
     2 {
     3     int i = 0;    //i在栈里面,生命周期就是其所处的大括号。
     4     return &i;
     5 }
     6 
     7 int main()
     8 {
     9     int *p = test();    //p指向了一个无效的地址。
    10     *p = 10;
    11     return 0;
    12 }
    13 --------------------------------------
    14 int *test()    //正确的代码
    15 {
    16     int *i = malloc(sizeof(int));    //i在堆里面。生命周期很长。主动调用free,堆空间释放或者进程结束,操作系统进行内存空间回收。 
    17     return i;
    18 }
    19 
    20 int main()
    21 {
    22     int *p = test();
    23     *p = 10;
    24     free(p);
    25     return 0;
    26 }

    2、

     1 void test(char *i)    //错误的代码
     2 {
     3     i = malloc(sizeof(char) * 100);        //i在栈里,指向了堆的地址。
     4 }
     5 
     6 int main()
     7 {
     8     char *p = NULL;
     9     test(p);    //实参的值可以传递给形参,形参的值发生改变,实参的值不会有影响。
    10     strcpy(p, "hello");
    11     free(p);
    12     return 0;
    13 }
    14 --------------------------------------
    15 void test(char **i)    //正确代码,通过二级指针解决上面这个问题。
    16 {
    17     *i = malloc(sizeof(char) * 100);
    18     
    19 }
    20 
    21 int main()
    22 {
    23     char *p = NULL;
    24     test(&p);
    25     strcpy(p, "hello");
    26     free(p);
    27     return 0;
    28 }
    29 小结:若想通过函数形参给实参分配内存,往往是通过二级指针来实现的。这是在c语言里面常用的技巧。
    30 --------------------------------------
    31 char *test()    //正确的代码
    32 {
    33     char *i = malloc(sizeof(char) * 100);    //i在堆里面。
    34     return i;
    35 }
    36 
    37 int main()
    38 {
    39     char *p = test();
    40     strcpy(p, "hello");
    41     free(p);
    42     return 0;
    43 }

    3、

     1 void test(char *i)    //错误的代码
     2 {
     3     strcpy(i, "hello");
     4 }
     5 
     6 int main()
     7 {
     8     test("hello");    //在栈里面:i = "hello"是常量。常量不可变。
     9     return 0;
    10 }
    11 --------------------------------------
    12 const char *test()    //正确的代码        函数的返回值是一个指向常量的指针。即该指针可以指向任何常量的地址。
    13 {
    14     return "hello";    //"hello"是常量。而且是字符串。所以它是const char *类型的。
    15 }
    16 
    17 int main()
    18 {
    19     const char *s = test();
    20     printf("%s
    ", s);
    21     return 0;
    22 }

    4、

     1 char *test()    //错误的代码        函数的返回值是指针变量。
     2 {
     3     return "hello";    //"hello"是常量。实际返回值是一个常量。
     4 }
     5 
     6 int main()
     7 {
     8     char *s = test();
     9     strcpy(s,"aabbcc");
    10     printf("%s
    ", s);
    11     return 0;
    12 }
    13 --------------------------------------
    14 const char *test()    //错误的代码    
    15 {
    16     const char a[] = "hello";    //数组a是自动变量,在栈里面。"hello"是常量,在静态区里面。从语法的角度const作用是:不能这样(a[0] = 'a';)去修改它的值。只读。
    17     return a;
    18 }
    19 
    20 int main()
    21 {
    22     const char *s = test();
    23     printf("%s
    ", s);
    24     return 0;
    25 }
    26 --------------------------------------
    27 const char *test()    //正确的代码
    28 {
    29     static char a[] = "hello";    //此时数组a在静态区里面。
    30     return a;
    31 }
    32 
    33 int main()
    34 {
    35     const char *s = test();    //从语法的角度const作用是:不能这样(s[0] = 'a';)去修改它的值。只读。
    36     printf("%s
    ", s);
    37     return 0;
    38 }

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

  • 相关阅读:
    bootstrap table
    C# 解压
    上传图片并预览
    前端提交后台一般处理文件
    [剑指offer] 60. 把二叉树打印成多行
    [剑指offer] 59. 按之字形顺序打印二叉树
    [剑指offer] 58. 对称的二叉树
    [剑指offer] 57. 二叉树的下一个结点
    [剑指offer] 56. 删除链表中重复的结点
    [剑指offer] 55. 链表中环的入口结点
  • 原文地址:https://www.cnblogs.com/chenmingjun/p/8296498.html
Copyright © 2020-2023  润新知