如果我在堆栈中保留一个指向某个值的指针,那么C标准是否在堆栈中保留函数的返回结构?

如果我在堆栈中保留一个指向某个值的指针,那么C标准是否在堆栈中保留函数的返回结构?,c,language-lawyer,C,Language Lawyer,以这个有争议的代码为例 struct X { int arr[1]; float something_else; }; struct X get_x(int first) { struct X ret = { .arr = { first } }; return ret; } int main(int argc, char **argv) { int *p = get_x(argc+50).arr; return *p

以这个有争议的代码为例

struct X {
     int arr[1];
     float something_else;
};
    
struct X get_x(int first)
{
     struct X ret = { .arr = { first } };
     return ret;
}

int main(int argc, char **argv) {
    int *p = get_x(argc+50).arr;
    
    return *p;
}
get_x
返回一个
struct x
。 我只对它的成员
arr
感兴趣。如果我只想要
arr
,为什么我要为整个结构创建一个局部变量

但是。。那代码正确吗


在所示的示例中,C标准是否知道将
get_x
的返回值保留在堆栈上,直到调用堆栈帧结束,因为我正在用指针窥视堆栈内部?

标准不允许您这样做

从函数返回的结构具有临时生存期,该生存期结束于它所用的表达式之外。因此,在初始化
p
之后,它立即指向一个对象,该对象的生存期已经结束,其值变得不确定。然后,尝试在以下语句中取消引用
p
(现在是不确定的)将触发

这在以下文件的第6.2.4p8节中有记录:

具有结构或并集类型的非左值表达式,其中 结构或联合包含数组类型的成员 (递归地包括所有包含结构的成员和 联合)是指具有自动存储持续时间和 短暂的一生。它的生存期从表达式求值时开始,其初始值是表达式的值。
它的生命周期在对包含完整 表达式或完整声明符结束。任何试图修改 具有临时生存期的对象将导致未定义的行为

其中,第6.2.4p2节规定了对象的生存期以及指向对象的指针在其生存期结束时的情况:

对象的生存期是程序的一部分 保证保留存储的执行过程 为了它。对象存在,具有恒定地址,并保留 它在整个生命周期中的最后一个存储值如果是对象 是指在其生命周期之外的行为 未定义。当 它指向(或刚刚过去)的对象到达其生命周期的末尾

如果要将函数的返回值分配给
struct X
的实例,则可以安全地访问该实例的
arr
成员

在所示的示例中,C标准是否知道将get_x的返回值保留在堆栈上,直到调用堆栈帧结束,因为我正在用指针窥视堆栈内部

不,它永远不能这样做,即使它“知道”这样做。当函数返回时,会弹出堆栈中的内容,“高于”该点的内容将变得未定义

即便如此

但是。。那代码正确吗

这是因为您没有创建指向被调用方堆栈框架中的结构的指针。您正在创建一个指向副本的指针,该副本是在返回struct by value时隐式创建的

从概念上讲,代码将把这个结构复制到调用方堆栈框架中保留的空间中(因为您专门调用一个返回结构的函数,在一般情况下,该值不能在寄存器中返回)。在实践中,优化编译器可能会在寄存器中返回它(如果您的计算机的寄存器可以适合包含int和float的结构),直接在调用方的堆栈帧中构建它(可以很容易地找到正确的位置,作为与被调用方堆栈帧的基部的偏移量),并四处移动内存(完全可以接受破坏性的重叠移动操作,因为“内存内容现在未定义”等)


…但只有这一部分,正如@dbush所指出的那样。正确地创建副本(即,有足够长的生命周期来使用这种方式)从函数中返回的值将需要是一个LValuy。概念上,编译器一旦完成了检索<代码> .ARR 成员,就可以从堆栈中弹出该副本。实际上,堆栈指针不会被调整,但是一个优化编译器会认为栈的一部分可以自由地用于其他局部变量。 操作目标与代码不同

我只对它的成员
arr
感兴趣。如果我只想
arr
,为什么我要为整个结构创建一个局部变量

成员
.arr
是一个数组。因此
int*p=get_x(argc+50)。arr;
不会复制数组
.arr
,而是将
.arr[0]
的地址复制到
p
。复制地址不会实现“只对其成员感兴趣”
arr
”。如果要在
.arr
中保存数据,请复制数据

要仅复制数组
.arr
而不是整个
struct X
,请使用
memcpy()

如果我在堆栈中保留一个指向某个值的指针,那么C标准是否在堆栈中保留函数的返回结构

不,如果结构是非l值,则不是,这意味着您在从函数返回后没有将其存储到变量中

在所示的示例中,C标准是否知道将get_x的返回值保留在堆栈上,直到调用堆栈帧结束,因为我正在用指针窥视堆栈内部

否。请阅读中的C标准参考

问题不在于
get_x()
函数——这很好。相反,在原始问题和下面的示例1中的错误代码中,问题只在于返回值
struct x
(返回值
get_x()
)不是l值(分配给变量),因此它是短暂的,这意味着它的存储持续时间在计算
int*p=get_x(argc+50).arr;
行后结束。因此,
return*p
中的
*p
是未定义的行为,因为它访问内存的时间为<
int my_copy[sizeof (struct X){0}.arr / sizeof *(struct X){0}.arr];
memcpy(my_copy, get_x(argc+50).arr, sizeof my_copy);

return my_copy[0];
int *p = get_x(argc+50).arr;
return *p;
<source>:15:14: warning: temporary whose address is used as 
value of local variable 'p' will be destroyed at the end of 
the full-expression [-Wdangling]
    int *p = get_x(argc+50).arr;
             ^~~~~~~~~~~~~~
1 warning generated.
ASM generation compiler returned: 0
<source>:15:14: warning: temporary whose address is used as 
value of local variable 'p' will be destroyed at the end of 
the full-expression [-Wdangling]
    int *p = get_x(argc+50).arr;
             ^~~~~~~~~~~~~~
1 warning generated.
Execution build compiler returned: 0
Program returned: 51
int p = get_x(argc+50).arr[0];
return p;
struct X x = get_x(argc+50);
int *p = x.arr;
return *p;
get_x(int):                              # @get_x(int)
        mov     eax, edi
        ret
main:                                   # @main
        push    rax
        add     edi, 50
        call    get_x(int)
        pop     rcx
        ret