【问题标题】:How does a compiler know if something is allocated on the stack or heap?编译器如何知道是否在堆栈或堆上分配了某些内容?
【发布时间】:2016-01-08 02:59:35
【问题描述】:

编译器如何知道是否在堆或堆栈上分配了某些东西,例如,如果我在函数中创建变量并返回变量的地址,编译器会警告我“函数返回局部变量的地址” :

#include <stdio.h>

int* something() {
    int z = 21;
    return &z;
}

int main() {
    int *d = something();
    return 0;
}

我理解为什么这是一个警告,因为当函数退出时,堆栈帧不再存在,如果您有一个指向该内存的指针并且您更改它的值,您将导致分段错误。我想知道的是编译器如何知道该变量是否通过分配内存。 malloc,或者它如何判断它是否是堆栈上的局部变量?

【问题讨论】:

  • 因为它放在那里。它知道哪些是局部变量。这就是它的用途,除此之外。

标签: c compiler-theory


【解决方案1】:

编译器会构建一个语法树,从中可以分析源代码的每个部分。

它构建了一个symbol table,它与每个定义了一些信息的符号相关联。这在许多方面都是必需的:

  • 查找未声明的标识符
  • 检查类型是否可转换
  • 等等

一旦你有了这个符号表,就很容易知道你是否试图返回一个局部变量的地址,因为你最终会得到一个类似的结构

ReturnStatement
     + UnaryOperator (&)
           + Identifier (z)

因此编译器可以轻松检查标识符是否为本地堆栈变量。

请注意,这些信息理论上可以沿着分配传播,但实际上我认为很多编译器都不会这样做,例如,如果你这样做

int* something() {
    int z = 21;
    int* pz = &z;
    return pz;
}

警告消失。通过静态代码流分析,您可以证明pz 只能引用局部变量,但实际上这不会发生。

【讨论】:

  • 只是一个错字类型更正(没有双关语):return &amp;pz; -> return pz;
【解决方案2】:

你问题中的例子很容易理解。

int* something() {
    int z = 21;
    return &z;
}
  1. 查看return 语句中的表达式。它采用标识符z 的地址。
  2. 找出z 的声明位置。哦,它是一个局部变量。

并非所有情况都像这种情况一样简单,如果您编写了足够奇怪的代码,您可能会欺骗编译器给出误报或误报。

如果您对这类内容感兴趣,您可能会喜欢观看 CppCon'15 上的一些演讲,其中 C++ 代码的静态分析非常重要。一些精彩的演讲:

【讨论】:

  • 在 return &z 之后,函数退出,然后如果您尝试访问该地址,您可能会得到一些其他(可能是垃圾)值。为避免这种情况,您可以将“int z 声明为静态”;
  • @AnilKumar 函数不正确,这就是重点。问题是编译器或其他静态分析器如何检测这种错误。
【解决方案3】:

编译器知道哪个内存块保存了当前堆栈。每次调用函数时,它都会创建一个新堆栈并适当地移动前一帧和堆栈指针,从而有效地为其提供内存中当前堆栈的起点和终点。鉴于该设置,检查您是否尝试返回指向即将被释放的内存的指针相对简单。

【讨论】:

    【解决方案4】:

    我想知道的是编译器如何知道该变量是否为 通过分配内存。 malloc,或者它如何判断它是否是本地的 栈上的变量?

    编译器必须分析所有代码并从中生成机器代码。

    当需要调用函数时,编译器必须将参数压入堆栈(或为其保留寄存器),更新堆栈指针,查看是否有局部变量,初始化堆栈上的变量并更新堆栈再次指针。

    很明显,编译器知道局部变量被推送到堆栈上。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-02
      • 2016-07-07
      • 2015-09-11
      • 2014-12-31
      • 1970-01-01
      • 1970-01-01
      • 2018-04-10
      • 2020-03-30
      相关资源
      最近更新 更多