一、

      栈作为一种特殊的数据结构而存在(“后入先出”存储),是一种只能在一端进行插入和删除操作的特殊线性表。    

     大多数CPU上的程序实现使用栈来支持函数调用操作。栈用来传递函数参数、存储返回信息、临时保存寄存器原有值以用于回复以及存储局部数据。

     栈有很多自己的特性,它具有记忆功能,对栈的插入与删除操作中,不需要改变栈底指针;而且栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。因此栈作用就是用来保持栈帧的活动记录(即函数调用)。

     栈相对整个系统而言,调用栈相对某个进程而言,栈帧则是相对某个函数而言,调用栈就是正在使用的栈空间,由多个嵌套调用函数所使用的栈帧组成。

    如果C语言的地址符'&'被应用到一个局部变量时,栈则需要为该变量生成一个地址值即变量的地址值分配一个空间。

    栈帧 --- 程序运行中数据的更改问题_栈帧

二、栈帧

     栈帧表示程序的函数调用记录,而栈帧又是记录在栈上面,很明显栈上保持了N个栈帧的实体,那就可以说栈帧将栈分割成了N个记录块,但是这些记录块大小不是固定的,因为栈帧不仅保存诸如:函数入参、出参、返回地址和上一个栈帧的栈底指针等信息,还保存了函数内部的自动变量(甚至可以是动态分配内存,alloca函数就可以实现,但在某些系统中不行),因此,不是所有的栈帧的大小都相同。

     单个函数调用操作所使用的栈部分称为栈帧结果。栈帧结构的两端由两个指针来指定。寄存器ebp通常用作栈帧的指针、esp用作栈的指针。esp随着数据的入栈和出栈。因此对于函数中大部分数据的访问都是通过基于帧帧指针ebp来实现。

    ebp指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部; esp所指的栈帧顶部和系统栈的顶部是同一个位置。

    栈帧 --- 程序运行中数据的更改问题_栈帧_02

例:

程序代码:

#include<stdio.h>
#include<stdlib.h>
int stack_test(int a,int b)
{
    printf("before write: 0x%x\n",b);
    int *p=&a;
    p++;
    *p=0xdddd;
    printf("after write: 0x%x\n",b);
    int c=0xcccc;
    return c;
}
int main()
{
    int a=0xaaaa;
    int b=0xbbbb;
    int ret=stack_test(a,b);
    printf("You should run here\n");
    return 0;
}

运行结果:

     栈帧 --- 程序运行中数据的更改问题_栈帧_03

结果分析:

     程序运行中数据的改变:

    栈帧 --- 程序运行中数据的更改问题_栈帧_04


        

    程序运行前,&b=0xbbbb;

    当程序运行后,系统重新分配一个临时空间。*p=&a,即此时临时拷贝a至新的内存空间;p++,*p向上移动一位,而此时*p的地址被赋予0xdddd.

    而栈以“后入先出”存储,即此时*p是该新栈帧的底部,先一步出去,因而&b=*p=0xdddd.

例:

程序如下;

#include<stdio.h>
#include<stdlib.h>
int bug()
{
    system("reboot");
    exit(0);
}
int stack_test(int a,int b)
{
    int *p=&a;
    p--;
    *p=bug();

    int c=0xcccc;
    return c;
}
int main()
{
    int a=0xaaaa;
    int b=0xbbbb;
    int ret=stack_test(a,b);
    printf("You should run here\n");
    return 0;
}

运行结果:

     系统重启

结果分析:

     情况和上例类似,当程序运行到bug()时,程序内部函数system("reboot")会命令系统重启,而随后exit(0)以正常情况结束程序。