8

我已阅读以下帖子:

从函数返回一个堆分配的指针好吗?

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

例如:

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() 是否给出未定义的行为?

4

5 回答 5

7

如果返回的值被访问,它们都将是未定义的行为。因此,它们都不是“好的”。

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

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

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

然后,从 P5 开始,

其标识符被声明为没有链接且没有存储类说明符 static 的对象具有自动存储持续时间, [...]

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

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

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

于 2019-04-09T12:15:18.883 回答
2

两者testtest2()是等价的。它们返回一个实现定义的指针,您不能取消引用,否则 UB 随之而来。

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

于 2019-04-09T12:17:27.130 回答
1

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

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

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

数组与指针示例:

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

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

于 2019-04-09T12:35:07.633 回答
0

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

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

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

于 2019-04-09T12:17:08.777 回答
0

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

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


1这超出了问题的范围,但从技术上讲,为返回值创建了一个临时对象。临时对象的生命周期和语义在现代 C++ 中被更系统地分类。

于 2019-04-09T13:20:38.757 回答