2
#include<stdio.h>

int* f1() { 
    int k =3; 
    return &k;
}

int main() { 
     int *z = f1(); 
     printf("%d",*z); 
}

OUTPUT 为 3。我想知道 f1() 的堆栈解除绑定何时发生。

4

4 回答 4

7

函数 f1() 返回变量 k 的地址,但由于 k 在堆栈上,所以在括号后它应该从堆栈内存中展开变量 k

这正是代码所做的。但是,在您的示例中,由于没有时间将其用于其他用途,因此该值3仍保留在地址中。编译器不会生成代码来覆盖超出范围的变量,以便您可以使用诸如此类的程序测试它们是否超出范围。

您的程序调用未定义的行为,因此生成的代码的任何行为都是可接受的,包括显示3.


如果您对小型 C 示例的定义性感兴趣,这个免费提供的静态分析器对您的程序有以下说明:

$ frama-c -val t.c
…
t.c:3:[value] warning: locals {k} escaping the scope of f1 through \result
…
t.c:8:[kernel] warning: accessing left-value z that contains escaping addresses

第一个警告只是提供信息:分析器认为函数可以让局部变量的地址超出其范围。对于作为结果返回的地址,是否不应直接将其视为编程错误是有争议的。但是,不认为这个错误很严重,只发出一条信息性消息对于写入全局变量的地址是有意义的:如果程序随后避免使用存储在全局变量中的地址,这种做法是安全的。

第二个警告表示未定义的行为,因为使用了不确定的地址z

于 2013-07-07T13:07:56.253 回答
1

我不确定我理解你所说的堆栈解除绑定是什么意思。关键是,即使您调用的堆栈帧在您调用时f1()不再有效printf(),它也不太可能被覆盖,因此您的程序会给出(错误的)预期结果。

你可以做一个实验,改变你的程序,如下所示:

#include<stdio.h>

int* f1() { 
  int k =3; 
  return &k;
}

int* f2() { 
  int k = 4; 
  return &k;
}

int main() { 
  int *z = f1(); 
  int *y = f2(); 
  printf("%d %d",*z, *y); 
}

怎么了?请注意,无论您看到什么行为,这只是您的编译器的工作方式,并且不能保证另一个编译器也会这样做。

于 2013-07-07T13:07:06.960 回答
1

忘掉你对stackunwindunbind等术语的所有概念。就 C 语言而言,该程序调用未定义的行为,因为在对象的生命周期结束后(在封闭块的函数关闭。)}}

于 2013-07-07T13:19:11.793 回答
0

生成的机器代码可能如下所示:在从函数返回时,栈头指针递减(或递增,如果栈从高地址到低地址增长)变量的大小和所需的其他信息f1。调用函数 (main) 所需的所有内容都已分配,因此不需要堆栈上的额外空间,因此k当您取消引用由f1. 因此,取消引用仍然为您提供值 3。但是,您访问的地址不再是堆栈的一部分。

stack within f1                            after returning from f1

       | ...            |                  | ...            |
SP---> | 3              | k                | 3              |
       | return address |                  | return address |
       | ...            |             SP-->| ...            |

SP 指向栈顶。从函数返回可能只是改变 SP。SP 之上的所有内容都可以视为未使用,但可能不会被例如零覆盖,因此它可能包含“旧”内容。

于 2013-07-07T13:11:42.970 回答