1

程序中栈

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

clipboard.png

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

clipboard.png

在程序中,栈是一种行为,先进后出;
其它数据信息:函数调用时产生的临时变量等;
ebp 指向函数调用后的返回地址
esp 栈顶指针。

函数调用过程

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

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

clipboard.png

函数调用的栈变化 一

  • 从 main() 开始运行

clipboard.png

函数调用的栈变化 二

当 main() 调用 f()

clipboard.png

函数调用的栈变化 三

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

clipboard.png

ebp 指针向前读 4 个字节,即 esp 指针所需要的返回地址;
ebp 指针向后读 4 个字节,即 ebp 指针之前所指向的地址, ebp 返回。

函数调用栈的数据

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

clipboard.png

编程实验:指向栈数据的指针

Test_1.c

#include <stdio.h>

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

void f()
{
    int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int i = 0;

    int* pointer = g();
    
    for(i=0; i<10; i++)  
    {
        a[i] = pointer[i];
    }
    
    for(i=0; i<10; i++)
    {
        printf("%d\n", a[i]);
    }
}

int main()
{
    f();
    
    return 0;
}
编译输出:
main.c: In function ‘g’:
main.c:7: warning: function returns address of local variable

运行输出:
0
0
0
0
0
0
0
0
0
0

分析:
g() 函数返回时, pointer 指向的栈空间的int[10]并没有立即发生改变,输出原始值。

Test_2.c

#include <stdio.h>

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

void f()
{
    int i = 0;

    int* pointer = g();
    
    for(i=0; i<10; i++)
    {
        printf("%d\n", pointer[i]);
    }
}

int main()
{
    f();
    
    return 0;
}
编译输出:
main.c: In function ‘g’:
main.c:7: warning: function returns address of local variable

运行输出:
0
16117748
0
0
-1077848536
14998560
-1077848488
14998560
16119008
134513936

分析:
pointer 指向的栈空间的int[10]发生改变,输出非原始值。

两次输出结果不同,发生了什么?

g() 函数返回时,所对应活动记录被释放,但此时并没有发生函数调用,因此数据没有发生改变。
当调用 printf 函数时,printf 函数需要在栈上建立对应的活动记录,g() 之前的活动记录因此被改变,所以指针指向的内存空间值发生改变。pointer 指向的内存空间不在有意义,成为野指针。

不能返回局部变量的地址和局部数组的数组名!

程序中的堆

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

为什么有了栈还需要堆?

栈上的数据在函数返回后就会被释放掉,无法传递到函数外部,如:局部数组

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

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

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

clipboard.png

分析:

  1. 每个节点下的内存都为 12byte、100byte、50byte....
  2. 当申请内存时,系统遍历空闲链表节点,查看所需内存大小与哪一个节点下内存大小最接近
  3. 到此节点下查找可用单元,查找到之后返回对应单元地址

在空闲链表管理法中,存在查找所需内存大小与哪一节点最接近的操作,这将导致 malloc 实际分配的内存可能会比请求的多。但我们不能依赖这种行为,因为在不同的系统中,对堆空间的管理方式可能是不同的。

程序中的静态存储区

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

编程实验: 静态存储区的验证

#include <stdio.h>

int g_v = 1;

static g_vs = 2;

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

int main()
{
    printf("%p\n", &g_v);
    
    printf("%p\n", &g_vs);
    
    f();
    
    return 0;
}
输出:
0x804a014
0x804a018
0x804a01c

分析:
在不同地方定义的全局变量、静态局部变量顺序排放在一起;
静态存储区的大小及位置在编译期就被确定。

小结

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

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

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧