我看过后面的帖子:
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()是否给出了未定义的行为?
如果访问返回值,它们都将是undefined behaviour。所以,他们都没有“好”。
您正在尝试返回指向块范围变量的指针,该变量具有auto
存储持续时间。因此,一旦范围结束,变量的生命周期即将结束。
引用C11
,章节§6.2.4/ P2,关于寿命(强调我的)
对象的生命周期是程序执行的一部分,在此期间保证为其保留存储。存在一个对象,具有一个常量地址,并在其整个生命周期内保留其最后存储的值。如果一个对象在其生命周期之外被引用,则行为未定义[...]
然后,从P5,
声明标识符没有链接且没有存储类说明符静态的对象具有自动存储持续时间,[...]
和
对于没有可变长度数组类型的此类对象,其生命周期从entry进入与其关联的块,直到该块的执行以任何方式结束。 [...]
因此,在您的情况下,变量arr
具有自动存储,并且它的生命周期仅限于函数体。一旦地址返回给调用者,尝试访问该地址的内存将是UB。
哦,C标准中没有“堆栈”或“堆”,我们所拥有的只是变量的生命周期。
test
和test2()
都是等价的。它们返回一个实现定义的指针,你不能取消引用,否则UB会随之发生。
如果不取消引用返回的指针,则调用test()
或test2()
不会导致未定义的行为,但这样的函数可能不是很有用。
在进入函数时,新的堆栈帧被添加到堆栈中。堆栈帧是存储所有autos(在函数中声明的非静态变量)的地方。当我们离开函数时,返回值被放置在CPU中的寄存器(通常是R0)中,然后减少堆栈指针以移除堆栈帧。然后我们将控制返回到我们调用函数的位置,我们从寄存器中获取返回值。
所以在这种情况下你有int arr[5]
,因为程序进入函数时,新的堆栈帧被添加到堆栈中。在这个堆栈帧中,存在一个数组中5个整数的存储器,变量arr
现在确实等同于指向数组中第一个元素的指针。当你返回变量arr
时,你返回一个指向堆栈帧中数据的指针,当函数退出并返回上一个函数时,堆栈指针会被减少,以删除刚刚退出的函数的堆栈帧。
指针仍指向我们以前分配过数组的内存中的那个位置。因此,当堆栈增加时,arr
指向的内存将被覆盖。更改返回值指向的数据可能会导致一些非常“激动”的事情发生,因为我们不知道现在何时使用内存。
数组与指针示例:
char arr[5];
char * ptr = arr;
在这种情况下,编译器知道arr
的大小,并且不知道ptr
的大小,因此我们可以执行sizeof(arr),编译器将在编译时进行计算。说到运行时,它们在内存中是等价的。
两种情况在技术上都是相同的。
在这两种情况下,都会返回指向arr的指针。虽然返回指针的值确实指向用于包含arr的内存,但arr已从内存中释放。
因此,有时当你访问指针时,你仍然会在那里找到arr的内容,这些内容恰好没有被覆盖。其他时候,您可能会在覆盖此内存后访问它,并获取未定义的数据甚至分段错误。
你似乎仍然被指针作为一个自动变量而感到困惑,所以你担心即使它指向一些有效的内存(比如一个静态数组),返回它也是无效的。
重要的是要记住,在C中,所有参数和返回值传递都是通过值完成的。如果你像return p;
那样“返回一个指针”,它就像你“返回一个整数”一样,就像在return i;
中一样:变量的值被复制到某处并由调用者获得。在i
的情况下,该值可能是42;在p
的情况下,该值可能是3735928559(或换句话说,0xdeadbeef)。该值表示存储器中的位置,例如,你的数组在它停止存在之前就已存在,因为函数返回了。当你复制它时,地址不会改变超过42个变化,并且完全独立于曾经包含它的变量p
的生命周期 - 毕竟它是在时间上复制出来的。
1这超出了问题的范围,但是 技术上 从概念上讲,为返回值创建临时对象。在现代C ++中,临时工的生命周期和语义更加系统地分类。