首页 > 系统相关 >内存管理下的栈 stack

内存管理下的栈 stack

时间:2022-09-23 16:24:24浏览次数:54  
标签:Obj 函数 管理 int C++ 内存 foo stack

在内存管理的语境下,指的是函数调用过程中产生的本地变量和调用数据的区域。这个栈和数据结构理的栈高度相似,都满足后进先出LIFO

看一段代码:


void foo(int n)
{
  …
}

void bar(int n)
{
  int a = n + 1;
  foo(a);
}

int main()
{
  …
  bar(42);
  …
}

在这段代码执行过程中的栈的变化:

在上边的示例中,栈是向上增长的。在包括 x86 在内的大部分计算机体系架构中,栈的增长方向是低地址,因而上方意味着低地址。任何一个函数,根据架构的约定,只能使用进入函数时栈指针向上部分的栈空间。当函数调用另外一个函数时,会把参数也压入栈里(我们此处忽略使用寄存器传递参数的情况),然后把下一行汇编指令的地址压入栈,并跳转到新的函数。新的函数进入后,首先做一些必须的保存工作,然后会调整栈指针,分配出本地变量所需的空间,随后执行函数中的代码,并在执行完毕之后,根据调用者压入栈的地址,返回到调用者未执行的代码中继续执行。

本地变量所需的内存就在栈上,跟函数执行所需的其他数据在一起。当函数执行完成之后,这些内存也就自然而然释放掉了。我们可以看到:

栈上的分配极为简单,移动一下栈指针而已。

栈上的释放也极为简单,函数执行结束时移动一下栈指针即可。

由于后进先出的执行过程,不可能出现内存碎片。

图 2 中每种颜色都表示某个函数占用的栈空间。这部分空间有个特定的术语,叫做栈帧(stack frame)。

前面例子的本地变量是简单类型,C++ 里称之为 POD 类型(Plain Old Data)。对于有构造和析构函数的非 POD 类型,栈上的内存分配也同样有效,只不过 C++ 编译器会在生成代码的合适位置,插入对构造和析构函数的调用。

这里尤其重要的是:编译器会自动调用析构函数,包括在函数执行发生异常的情况。在发生异常时对析构函数的调用,还有一个专门的术语,叫栈展开(stack unwinding)。事实上,如果你用 MSVC 编译含异常的 C++ 代码,但没有使用上一讲说过的 /EHsc 参数,编译器就会报告:

下面是一段简短的代码,可以演示栈展开:


#include <stdio.h>

class Obj {
public:
  Obj() { puts("Obj()"); }
  ~Obj() { puts("~Obj()"); }
};

void foo(int n)
{
  Obj obj;
  if (n == 42)
    throw "life, the universe and everything";
}

int main()
{
  try {
    foo(41);
    foo(42);
  }
  catch (const char* s) {
    puts(s);
  }
}

 

也就是说,不管是否发生了异常,obj 的析构函数都会得到执行。

在 C++ 里,所有的变量缺省都是值语义(值语义就是一个对象被系统标准的复制方式复制后,与被复制的对象之间毫无关系,可以彼此独立改变互不影响)——如果不使用 * 和 & 的话,变量不会像 Java 或 Python 一样引用一个堆上的对象。对于像智能指针这样的类型,你写 ptr->call() 和 ptr.get(),语法上都是对的,并且 -> 和 . 有着不同的语法作用。而在大部分其他语言里,访问成员只用 .,但在作用上实际等价于 C++ 的 ->。这种值语义和引用语义的区别,是 C++ 的特点,也是它的复杂性的一个来源。要用好 C++,就需要理解它的值语义的特点。

 

标签:Obj,函数,管理,int,C++,内存,foo,stack
From: https://www.cnblogs.com/fuqiangblog/p/16723133.html

相关文章