【问题标题】:why windows use stacks for storing the local variables?为什么Windows使用堆栈来存储局部变量?
【发布时间】:2013-07-05 01:16:57
【问题描述】:

为什么 C 使用堆栈来存储局部变量?这只是为了拥有独立的内存空间,还是为了拥有超出范围后自动清除所有局部变量和对象的功能?

我还有几个相同的问题,

问题1)如何从指令部分引用局部变量。考虑NewThreadFunc是createThread函数调用的函数。

DWORD WINAPI NewThreadFunc(PVOID p_pParam)
{
int l_iLocalVar1 = 10;
int l_iLocalVar2 = 20;

int l_iSumLocalVar = l_iLocalVar1 + l_iLocalVar2;
}

这个线程的堆栈看起来像这样,

| p_pParam       |
| NewThreadFunc()|
| 10             |
| 20             |
| 30             |
|                |
.
.
.

现在我的问题是,在执行这个函数时,CPU 怎么知道局部变量的地址(l_iSumLocalVarl_iLocalVar1l_iLocalVar2)?这些变量不是它们存储必须从中获取值的地址的指针。我的问题是上面的堆栈。

问题 2) 如果该函数进一步调用任何其他函数,堆栈将如何处理它?据我所知,堆栈会进一步分裂。如果这是真的,被调用函数的局部变量如何从被调用函数中隐藏起来。基本上局部变量是如何维护作用域规则的?

我知道这些可能是非常基本的问题,但有些问题我想不出答案。

【问题讨论】:

  • 这和Windows有什么关系?
  • C 没有指定(局部)变量的存储位置或方式,因此它不一定是堆栈。
  • 确实如此,但实际上每个实现都使用堆栈,因为这就是世界上所有硬件的工作方式。你知道有什么例外吗?
  • 抱歉,它不是专门针对 windows 的。。

标签: c stack internals variable-address


【解决方案1】:

首先,将堆栈用于局部变量的不是“Windows”。它与“Windows”或任何其他操作系统完全无关。是您的编译器执行此操作。没有人强迫您的编译器为此目的使用系统堆栈,但通常这是实现局部变量的最简单和最有效的方法。

其次,编译器使用堆栈来存储局部变量(即系统提供的堆栈或编译器实现的堆栈),因为类似堆栈的存储非常精确地匹配语言要求的局部变量语义。局部变量的存储持续时间由它们的声明性区域(块)定义,这些区域(块)严格相互嵌套。这立即意味着局部变量的存储持续时间遵循 LIFO 原则:后进先出。因此,首先想到的是使用堆栈(一种 LIFO 数据结构)来分配具有 LIFO 存储持续时间的对象。

局部变量通常通过它们的偏移量从当前活动堆栈帧的开始处寻址。编译器在编译时知道每个局部变量的确切偏移量。编译器通过以下方式生成将为当前函数分配堆栈帧的代码:1) 在进入函数时记住堆栈指针的当前位置(假设它被存储在寄存器 R1 中)和 2) 移动当前堆栈指针按存储函数的所有局部变量所需的量。一旦以这种方式分配堆栈帧,您的局部变量l_iLocalVar1l_iLocalVar2l_iSumLocalVar 将简单地通过地址R1 + 6R1 + 10R1 + 14 访问(我使用了任意偏移量)。换句话说,局部变量不会被特定的地址值访问,因为这些地址在编译时是未知的。而是通过计算的地址访问局部变量。它们被计算为一些运行时基地址值 + 一些编译时偏移值。

【讨论】:

  • 我喜欢你的回答。仍然不清楚的一件事是如何维护变量的范围?你说它是在堆栈的帮助下得到照顾的,当超出范围时它会弹出。所以,这是否意味着即使是本地范围,即for 循环中的花括号,创建了一个新的子 stach,但堆栈指针不会移动到新的堆栈基地址,从而使先前的局部变量以及新范围内的新变量都可用花括号... main() { int l_iVar1 = 2; for(;;) { int l_iVar2 = 5; } }
  • continue.. 在上面的代码示例中,我想指出 for 循环内两个变量都可见的范围区域。感谢您的时间@AndreyT
【解决方案2】:

为什么堆栈用于局部变量?

好吧,堆栈是一种易于使用的结构,可为临时变量保留空间。它的好处是当函数返回时它几乎会被自动删除。另一种方法是从操作系统分配内存,但这会导致严重的内存碎片。 堆栈可以很容易地被分配和再次释放,所以这是一个自然的选择。

【讨论】:

  • “另一种方法是从操作系统分配内存” - 嗯?你认为堆栈的内存来自哪里?
  • @H2CO3 我的意思是,您也可以使用 malloc/free 调用在内部分配自动变量,但这会带来额外的开销。当然,所有内存都来自操作系统。没有它,堆栈已经可用。
【解决方案3】:

所有变量地址都与每次函数调用或返回时递增的堆栈指针相关。分配和清理这些变量使用的内存的快速简便方法。

【讨论】:

    【解决方案4】:
    1. 通常,系统调用约定会保留一个寄存器用作“堆栈指针”。局部变量访问是相对于该寄存器的值进行的。由于每个函数都必须知道它使用了多少堆栈空间,因此编译器会发出代码以确保堆栈指针根据每个函数的要求正确调整。

    2. 局部变量的范围仅由编译器强制执行,因为它是一种语言结构,与硬件无关。您可以将堆栈变量的地址传递给其他函数,它们会正常工作。

    【讨论】:

    • 对2最后一句话的注释:将局部变量的地址传递给其他函数时要小心,因为当变量超出范围时,这些地址将变得无效。
    • @Kninnug:正确。指向堆栈变量的规则:你可以“传入”,但不能“传出”。也就是说,你可以将它作为参数传递给你调用的东西,但你不能将它作为返回值传递出去,因为变量会超出范围。
    • @JoeZ,这取决于您“传入”的函数是否会保持对指针的引用。我觉得你的规则太简单了。
    • @Kninnug Nah。 “传递”变量与“返回”变量不同。当您在调用堆栈上推送内容时(即使用一些参数调用新函数),它将被保留直到弹出(当最后调用的函数返回时发生)。 Carl Norum 所说的完全正确。
    • @H2CO3 这就是为什么我说“传递变量的地址”。 Carl Norum 确实是正确的,但是被调用的函数可以将地址存储在其他地方,之后在原始函数返回时可能会使用它。我的观点是,当您获取本地地址并将它们超出其范围时,您应该小心。这不是错误的,只是有潜在的危险。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-15
    • 2015-06-23
    • 2015-01-09
    • 1970-01-01
    • 2020-06-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多