39-程序中的三国天下

注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。

测试环境:Ubuntu 10.10

GCC版本:4.4.5

一、程序中的栈(行为:后进先出)

1) 栈是现代计算机程序里最为重要的概念之一

2) 栈在程序中用于维护函数调用上下文

3) 函数中的参数和局部变量存储在栈上

esp:栈指针

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

    - 参数

    - 返回地址

    - 局部变量

    - 调用上下文

    - ...

ebp:扩展基址指针

二、函数调用过程

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

    - 调用函数的活动记录位于栈的中部

    - 被调函数的活动记录位于栈的顶部(被调用的函数g()需要出栈,放在栈顶)

2) 函数调用的栈变化一

从main()开始运行

3) 函数调用的栈变化二

当main()调用f()

f()的Old ebp是为了执行完函数后跳回进入函数main()时的地址。

4) 函数调用的栈变化三

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

f()通过ebp返回之前的地址。esp也会回退,但是原来栈中的内存数据没有立即消失。之前提到过函数不要返回局部变量地址或者局部数组,是因为如果main()函数中的f()返回地址后,如果立即调用其它函数,可能会产生野指针。新的函数覆盖到了f()当时存储的区域!

5) 函数调用栈上的数据

1. 函数调用时,对应的栈空间在函数返回前是专用的

2. 函数调用结束后,栈空间将被释放数据不再有效

编程实验
指向栈数据的指针
39-1.c
#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();    //想用pointer指针指向g()中的数组
    //为了证明栈空间数据返回可能出错,注释掉
    for(i=0;i<10;i++)
    {
        b[i] = pointer[i];
    }
    
    for(i=0; i<10; i++)
    {
        printf("%d\n",b[i]);      //为了证明栈空间数据返回可能出错,注释掉,修改如下
        //printf("%d\n",pointer[i]);    //这样出现野指针
    }
}

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

操作:

1) gcc 39-1.c -o 39-1.out编译有警告:

39-1.c:7:2: warning: function returns address of local variable [-Wreturn-local-addr]
  return a;
  ^
警告:函数返回局部变量地址(返回了局部数组地址)

运行程序:

0
0
0
0
0
0
0
0
0
0

分析:

        pointer指向的数组,每个元素数值都为0,因此这里打印0。但是g()函数返回后,栈空间中的值是会被改变。

注释掉:

39-1.c
#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();    //想用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;
}

编译有警告:

39-1.c:7:2: warning: function returns address of local variable [-Wreturn-local-addr]
  return a;
  ^
警告:返回局部变量地址

运行结果:

0
-1217593344
0
0
-1076373848
-1219029953
-1217590592
134514048
-1076373916
-1219030000

分析:

        g()函数在f()栈空间调用后被销毁,但是新调用printf()函数会覆盖掉刚才临时存储g()函数的栈空间,导致数据被修改。

三、程序中的堆

1) 堆是程序中一块预留的内存空间,可由程序自由使用

2) 堆中被程序申请使用的内存在被主动释放前将一直有效

        面试题:为什么有了栈还需要堆?

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

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

    -    头文件:malloc.h  <malloc.h>

    -    malloc--以字节的方式动态申请堆空间

    -    free--将堆空间归还给系统

4) 系统对堆空间的管理方式

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

        遍历内存大小,对比申请的内存和节点空间大小,如果大小接近就使用。之前提到为什么申请的堆空间比自己申请的大,因为链表节点占用一些大小!

四、程序中的静态存储区(编译时就知道大小,运行时才分配空间)rodata

1) 静态存储区随着程序的运行而分配空间

2) 静态存储区的生命周期直到程序运行结束

3) 在程序的编译期静态存储区的大小就已经确定

4) 静态存储区主要用于保存全局变量和静态局部变量

5) 静态存储区的信息最终会保存到可执行程序中

编程实验
静态存储区的验证
39-2.c
#include <stdio.h>

int g_v = 1;

static int g_vs = 2;    //static使g_vs只能在这个文件中使用

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;
}

操作:

1) gcc 39-2.c -o 39-2.out编译正确,打印结果:

0x804a014
0x804a018
0x804a01c

分析:

        这三个变量都放在静态存储区,编译阶段是连续的,所以三个变量地址才是连续的。

证实:大小和位置编程序编译期就确定了。

小结:

1) 静态存储区是程序中的三个基本数据区

    -    栈区主要用于函数调用的使用

    -    堆区主要是用于内存的动态申请和归还(不归还会导致内存泄露,程序运行越来越慢,直到崩溃)

    -    静态存储区用于保存全局变量和静态变量(编译阶段就固定了静态存储区大小)

发布了46 篇原创文章 · 获赞 1 · 访问量 1905

猜你喜欢

转载自blog.csdn.net/piaoguo60/article/details/104086203