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

时间:2019-04-09 12:12:43

标签: c return-value lifetime

我已阅读以下帖子:

Is returning a heap-allocated pointer from function OK?

这表明可以返回指向堆分配变量的指针。但是,指针在技术上是否是“堆栈分配的变量”,则该变量将在函数返回时被释放?

例如:

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不是指针,为什么返回满足函数返回类型的有效吗?

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

5 个答案:

答案 0 :(得分:5)

如果访问返回的值,它们都将是undefined behaviour。因此,它们都不是“ OK”。

您正试图返回一个指向块范围变量的指针,该变量的存储持续时间为auto。因此,一旦作用域结束,变量的生命周期就结束了。

引用C11,第6.2.4 / P2章,关于生存期强调矿山

  

对象的生存期是程序执行期间存储空间的一部分   保证为其保留。对象存在,具有恒定地址并保留   它在整个生命周期中的最后存储值。 如果在对象之外引用对象   一生中,行为是不确定的 [...]

然后,从P5开始,

  

一个其标识符声明为没有链接且没有存储类的对象   说明符静态具有自动存储期限, [...]

  

对于这样一个不具有可变长度数组类型的对象,其寿命会延长   从进入与其关联的块开始,直到该块的执行结束   任何方式。 [...]

因此,在您的情况下,变量arr具有自动存储功能,并且其生存期仅限于函数体。该地址返回给呼叫者后,尝试访问该地址处的内存将是UB。

哦,在C标准中没有“堆栈”或“堆”,我们所拥有的只是变量的生存期。

答案 1 :(得分:2)

testtest2()都是等效的。它们返回您不能取消引用的实现定义的指针,否则将导致UB。

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

答案 2 :(得分:1)

输入功能后,新的堆栈框架将添加到堆栈中。堆栈帧是存储所有auto(函数中声明的非静态变量)的位置。当我们离开函数时,返回值放置在CPU的一个寄存器(通常为R0)中,然后减小堆栈指针以删除堆栈帧。然后,将控制权返回到调用函数的位置,然后从寄存器中获取返回值。

因此,在这种情况下,您有int arr[5],当程序进入函数时,新的堆栈框架将添加到堆栈中。在此堆栈帧中,数组中有5个整数的内存,变量arr实际上确实等效于指向数组中第一个元素的指针。当您返回变量arr时,您将返回一个指向堆栈框架中数据的指针,当该函数退出并返回至上一个函数时,堆栈指针将减小以删除您所使用的函数的堆栈框架刚刚退出。

指针仍指向内存中我们之前分配了数组的位置。因此,当堆栈增加时,arr指向的内存将被覆盖。更改返回值指向的数据可能会导致某些非常“令人兴奋”的事情发生,因为我们不知道现在何时使用内存。

数组与指针示例:

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

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

答案 3 :(得分:0)

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

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

因此,有时在访问指针时,您仍会在此处找到arr的内容,这些内容刚好尚未被覆盖。有时,您可能会在此内存被覆盖后访问它,并得到未定义的数据甚至是分段错误。

答案 4 :(得分:0)

您仍然似乎对指针也是一个自动变量感到困惑,因此,即使返回指向某个有效内存(例如静态数组)的指针,您也担心它会无效。

重要的是要记住,在C中,所有参数和返回值的传递均通过值完成。如果您像return p;中那样“返回指针”,则其机制与如果您“返回整数”,例如return i;:变量的 value 会复制到某个地方并由调用方获取。对于i,该值可能是42;在p的情况下,值可以是3735928559(或换句话说,是0xdeadbeef)。该表示内存中的位置,例如因为函数返回,所以数组在不存在之前就已经存在。复制的地址超过42个更改时,该地址不会更改,并且完全独立于曾经包含该地址的变量p的生存期-毕竟是及时复制了它。< sup> 1


1 这超出了问题的范围,但从概念上技术上,将为返回值创建一个临时对象。在现代C ++中,临时者的生存期和语义得到了更系统的分类。