为什么在 C 允许的函数中返回堆栈分配的指针变量?

Why is returning a stack allocated pointer variable in a function allowed in C?

我已阅读以下post:

这表明指向堆分配变量的指针是 returned 是好的。但是,指针在技术上是 "stack allocated variable",然后会在 return 调用函数时被释放吗?

例如:

int* test(){
  int arr[5];
  int *ptr = arr;

  return ptr; //deallocated ptr?
}

int *test2(){
  int arr[5];

  return arr;
}

测试中

另外,arr是一个指针,指向某个新创建的int数组arr,指向&arr[0]对吗?如果arr不是指针,为什么return满足函数return类型才有效?

既然 ptr 和 arr 都应该是堆栈分配的,为什么代码只能在 test() 而不是 test2() 中工作? test() 是否给出未定义的行为?

如果访问 returned 值,它们都将是 undefined behaviour。所以,其中 none 个是 "OK"。

您正在尝试 return 一个指向具有 auto 存储持续时间的块范围变量的指针。所以,一旦范围结束,变量的生命周期就结束了。

引用 C11,章节 §6.2.4/P2,关于 生命周期强调我的

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined [...]

然后,从P5开始,

An object whose identifier is declared with no linkage and without the storage-class specifier static has automatic storage duration, [...]

For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way. [...]

因此,在您的情况下,变量 arr 具有自动存储功能,并且其生命周期仅限于函数体。一旦地址被 returned 给调用者,尝试访问该地址的内存将是 UB。

哦,C 标准中没有 "stack" 或 "heap",我们只有变量的生命周期。

这两种情况在技术上是相同的。

在这两种情况下,都会返回指向 arr 的指针。虽然返回指针的值确实指向了用于包含 arr 的内存,但 arr 已经从内存中释放了。

因此,有时当您访问指针时,您仍然会在那里找到 arr 的内容,这些内容只是碰巧还没有被覆盖。其他时候,你可能会在这段内存被覆盖后访问它,并得到未定义的数据甚至段错误。

testtest2() 是等价的。它们 return 一个你不能解除引用的实现定义的指针,否则 UB 随之而来。

如果您不取消引用 returned 指针,调用 test()test2() 不会导致未定义的行为,但这样的函数可能不是很有用。

进入一个函数后,一个新的栈帧被添加到栈中。堆栈帧是存储所有 autos(函数中声明的非静态变量)的地方。当我们离开函数时,return 值被放置在 CPU 中的寄存器(通常是 R0)中,然后减少堆栈指针以删除堆栈帧。然后我们 return 控制到我们调用函数的地方,我们从寄存器中得到 return 值。

所以在这种情况下你有 int arr[5],当程序进入函数时,一个新的栈帧被添加到栈中。在这个堆栈帧中,数组中有 5 个整数的内存,变量 arr 现在确实相当于指向数组中第一个元素的指针。当您 return 变量 arr 时,您正在 return 指向堆栈帧中数据的指针,当函数退出并且您 return 回到上一个函数时,堆栈然后减少指针以删除您刚刚退出的函数的堆栈帧。

指针仍指向内存中我们之前分配数组的位置。因此,当堆栈增加时,arr 指向的内存将被覆盖。更改 returned 值指向的数据可能会导致一些非常 "exciting" 的事情发生,因为我们不知道内存现在用于什么时候。

数组与指针示例:

char arr[5];
char * ptr = arr;

在这种情况下,编译器知道 arr 的大小,但不知道 ptr 的大小,因此我们可以执行 sizeof(arr) ,编译器将在编译时进行计算。到运行时,它们在内存中是等值的。

你似乎仍然对指针也是一个自动变量感到困惑,所以你担心 returning 它会无效,即使它指向一些有效的内存(比如,一个静态数组).

重要的是要记住,在 C 中,所有参数和 return 值的传递都是 按值完成的。 如果您 "return a pointer" 如 return p; 它与 "returned an integer" 中的机制完全相同,如 return i; 中:变量的 value 被复制到某处并由调用者获取。在 i 的情况下,该值可能是 42;在 p 的情况下,该值可能是 3735928559(或者换句话说,0xdeadbeef)。 value 表示内存中的位置,例如你的数组在它不复存在之前就存在了,因为函数returned。当你复制它时,地址不会改变超过 42 个变化,并且完全独立于曾经包含它的变量 p 的生命周期——毕竟,它是及时复制出来的。1


1这超出了问题的范围,但技术上概念上,为return 值。现代 C++ 对临时对象的生命周期和语义进行了更系统的分类。