【C语言进阶剖析】38.C语言中的栈、堆和静态存储区

119 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

文章目录

一、程序中的栈

二、函数的调用过程

三、函数调用的栈变化

四、函数调用栈上的数据

五、程序中的堆

六、程序中的静态存储区

七、小结


一、程序中的栈

  • 栈是现代计算机程序里最为重要的概念之一
  • 栈在程序中用于维护函数调用上下文
  • 函数中的参数和局部变量存储在栈上

编辑

  •  栈保存了一个函数调用所需的维护信息

    • 参数
    • 返回地址
    • 局部变量
    • 调用上下文

编辑

二、函数的调用过程

  • 每次函数调用都对应着一个栈上的活动记录

    • 调用函数的活动记录位于栈的中部
    • 被调函数的活动记录位于栈的顶部

编辑

三、函数调用的栈变化

  • 从main() 开始运行

编辑

  •  main() 调用 f()

编辑

  •  当从 f() 调用中返回 main()

编辑

四、函数调用栈上的数据

  • 函数调用时,对应的栈空间在函数返回前是专用的
  • 函数调用结束后,栈空间将被释放,数据不再有效

编辑

        下面看一个指向栈数据的指针: 

#include <stdio.h>

int* g()
{
    int a[10] = {0};
    return a;
}

void f()
{
    int i = 0;
    int b[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *pointer = g();
    
    for (i = 0; i < 10; i++)
    {
        b[i] = pointer[i];
    }
    for(i = 0; i < 10; i++)
    {
        printf("%d\n", b[i]);
    } 
}

int main()
{
    f();
    return 0;
}

         输出结果如下:

编辑

        如果把 

        for (i = 0; i < 10; i++)
{
b[i] = pointer[i];
}

        注释了,直接打印 pointer[i] 里面的数据,如下:

#include <stdio.h>

int* g()
{
    int a[10] = {0};
    return a;
}

void f()
{
    int i = 0;
    int b[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *pointer = g();
    /*
    for (i = 0; i < 10; i++)
    {
        b[i] = pointer[i];
    }
    */
    for(i = 0; i < 10; i++)
    {
        printf("%d\n", pointer[i]);
    } 
}

int main()
{
    f();
    return 0;
}

        输出结果如下:

编辑

         为什么直接打印 pointer[i] 里面的值会是这样呢?因为 pointer 指向的空间是栈空间,栈空间在 g() 函数返回之后,活动记录就被释放了。被释放后调用 printf 函数,printf 函数需要在栈上面建立一个活动记录。这个活动记录就会有 printf 函数的参数信息和返回值等,所以 pointer 所指向的内存里面的数据由于 printf 函数的调用被改变了。因此,不能返回局部变量的地址,不能返回局部数组的数组名。

五、程序中的堆

  • 堆是程序中一块预留的内存空间,可由程序自由使用
  • 堆中被程序申请使用的内存在被主动释放前将一直有效

        为什么有了栈还需要堆?
答: 栈上的数据在函数返回后就会被释放掉,无法传递到函数外部,如:局部数组

  • C语言程序中通过库函数的调用获得堆空间

    • 头文件:malloc.h
    • malloc -- 以字节的方式动态申请堆空间
    • free -- 将堆空间归还给系统
  • 系统对堆空间的管理方式

    • 空闲链表法,位图法,对象池法等等

编辑

        以 int* p = (int*)malloc(sizeof(int)); 为例,要申请 4 个字节的大小,遍历之后发现跟 5 Bytes 这个节点最接近,找到一个可以用的单元之后,就将这个单元的地址返还给了 p 指针。以前也提过使用 malloc 申请内存空间时返回的内存空间可能比申请的实际内存空间要大一点点,原因就是在空闲链表管理堆空间这样的系统里面,它会找最近的那个,找到后的一般都大于等于所需要的内存空间,假如 5 Bytes 这个节点下所有的空闲内存单元都用完的话,就会找 12 Bytes 节点下的内存单元,这样malloc 返回的内存空间就有可能比自己实际申请的内存空间要大。

六、程序中的静态存储区

  • 静态存储区随着程序的运行而分配空间
  • 静态存储区的生命周期直到程序运行结束
  • 在程序的编译期静态存储区的大小就已经确定
  • 静态存储区主要用于保存全局变量和静态局部变量
  • 静态存储区的信息最终会保存到可执行程序中

        下面看一个静态存储区的验证代码:

#include <stdio.h>

int g_v = 1;

static int g_vs  = 2;

void f()
{
    static int g_vl = 3;
    
    printf("%p\n", &g_vl);
}

int main()
{
    printf("%p\n", &g_v);
    
    printf("%p\n", &g_vs);
    
    f();
    
    return 0;
}

        输出结果如下:

编辑

         可以看到这三个地址是顺序存放的,因为这三个变量都是存放在程序的静态存储区,静态存储区在程序里面有固定的起始地址。 

七、小结

  • 栈,堆和静态存储区是程序中的三个基本数据区

    • 栈区主要用于函数调用的使用
    • 堆区主要是用于内存的动态申请和归还
    • 静态存储区用于保存全局变量和静态变量