编译器如何知道堆栈或堆上是否分配了某些内容?

How does a compiler know if something is allocated on the stack or heap?

编译器如何知道堆或堆栈上是否分配了某些内容,例如,如果我在函数中创建了一个变量并返回了该变量的地址,编译器会警告我 "function returns address of a local variable":

#include <stdio.h>

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

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

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

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

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

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

一旦你有了这个符号 table 就很容易知道你是否正在尝试 return 局部变量的地址,因为你最终有一个像

这样的结构
ReturnStatement
     + UnaryOperator (&)
           + Identifier (z)

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

请注意,这些信息在理论上可以随着赋值传播,但实际上我认为很多编译器都不会这样做,例如,如果您这样做了

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

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

What I wonder is how the compiler will know if that variable is allocating memory via. malloc, or how it can tell if it's a local variable on the stack?

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

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

很明显,编译器知道局部变量被压入到堆栈中。

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

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

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

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

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