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

时间:2016-01-08 02:59:36

标签: c compiler-theory

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

#include <stdio.h>

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

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

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

4 个答案:

答案 0 :(得分:2)

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

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

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

一旦你有这个符号表,很容易知道你是否试图返回一个局部变量的地址,因为你最终有一个像

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

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

请注意,这些信息在理论上可以按照任务传播,但在实践中我并不认为许多编译器会这样做,例如,如果你这样做

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

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

答案 1 :(得分:2)

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

int* something() {
    int z = 21;
    return &z;
}
  1. 查看return语句中的表达式。它采用标识符z
  2. 的地址
  3. 找出宣布z的位置。哦,这是一个局部变量。
  4. 并非所有情况都会像这一样容易,如果编写足够奇怪的代码,很可能会欺骗编译器给出误报或否定。

    如果你对这种东西感兴趣,你可能会喜欢看CppCon'15中给出的一些谈话,其中C ++代码的静态分析是一个大问题。一些非凡的会谈:

答案 2 :(得分:1)

  

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

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

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

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

答案 3 :(得分:1)

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