如果我在其中保留指向某个值的指针,C 标准是否会在堆栈上保留函数的 return 结构?
Does the C standard keep the return struct of a function on the stack if I keep a pointer to a value inside it?
以这段有争议的代码为例。
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
return一个struct X
。
我只对它的成员 arr
感兴趣。如果我只想要 arr
...
为什么要为整个结构创建局部变量
但是..该代码正确吗?
在所示示例中,C 标准是否知道将 get_x
的 return 值保留在堆栈中,直到调用堆栈帧结束,因为我正在使用指针?
标准不允许您做的事情。
从函数中 returned 的结构具有 临时生命周期,它在使用它的表达式之外结束。所以在 p
初始化之后,它指向一个生命周期结束的对象,它的值变为indeterminate。然后尝试在以下语句中取消引用 p
(现在不确定)触发 undefined behavior.
这在 C standard 的第 6.2.4p8 节中有记录:
A non-lvalue expression with structure or union type, where
the structure or union contains a member with array type
(including, recursively, members of all contained structures and
unions) refers to an object with automatic storage duration and
temporary lifetime. Its lifetime begins when the expression is evaluated and its initial value is the value of the expression.
Its lifetime ends when the evaluation of the containing full
expression or full declarator ends. Any attempt to modify an
object with temporary lifetime results in undefined behavior.
对象的生命周期以及指向对象的指针在其生命周期结束时发生的情况在第 6.2.4p2 节中指定:
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. The value of a pointer becomes indeterminate when the
object it points to (or just past) reaches the end of its lifetime
如果您要将函数的 return 值分配给 struct X
的实例,那么您可以安全地访问该实例的 arr
成员。
In the shown example, does the C standard know to keep the return value of get_x on the stack until the end of the calling stack frame because I'm peeking inside it with a pointer?
不,它永远不能这样做,即使它“知道”这样做。当一个函数 returns 时,事情会从堆栈中弹出,并且该点“上方”的任何内容变得未定义。
即便如此,
But.. is that code correct?
That part is! 这是因为您没有创建指向被调用者堆栈帧 中的结构的指针。您正在创建一个指向 副本 的指针,它是在您 return 按值编辑结构时隐式创建的。
从概念上讲,代码会将此结构复制到调用者堆栈帧中保留的 space(因为您专门调用了一个 return 结构的函数,在一般情况下,该值可以'被return编入寄存器)。实际上,优化编译器可能 return 它在一个寄存器中(如果你的机器的寄存器可以容纳一个包含 int 和 float 的结构),直接在调用者的堆栈框架中构造它(正确的位置很容易发现作为从被调用者堆栈框架的基址的偏移量),随机播放内存(破坏性的重叠移动操作是可以接受的,正是因为“内存内容现在未定义”的事情),等等
...但正如@dbush 所指出的那样,只有那一部分。要正确创建一个副本(即,有足够长的生命周期来使用这种方式),函数中的 return 值需要是一个左值。从概念上讲,一旦完成检索 .arr
成员,编译器就可以将该副本从堆栈中弹出。实际上,堆栈指针不会得到调整,但优化编译器会认为堆栈的那部分可以免费用于其他局部变量。
OP 目标与代码不同。
I'm only interested in its member arr
. Why would I make a local variable for the entire struct if I only want arr
... (?)
成员.arr
是一个数组。所以int *p = get_x(argc+50).arr;
并没有复制数组.arr
,而是将.arr[0]
的地址复制到p
中。复制地址不满足“只对其成员感兴趣arr
”。如果你想要.arr
中的数据,复制数据。
要仅复制数组 .arr
而不是整个 struct X
,请使用 memcpy()
.
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];
Does the C standard keep the return struct of a function on the stack if I keep a pointer to a value inside it?
不,如果结构是非左值,则不是,这意味着您不是在从函数返回后将其存储到变量中。
In the shown example, does the C standard know to keep the return value of get_x on the stack until the end of the calling stack frame because I'm peeking inside it with a pointer?
没有。阅读 .
中的 C 标准参考
问题不在于 get_x()
函数——没关系。相反,在原始问题和下面的示例 1 中的错误代码中,问题只是按值返回 struct X
(由 get_x()
返回)不是左值(分配给一个变量),所以它是短暂的,这意味着它的存储持续时间在评估 int *p = get_x(argc+50).arr;
行后结束。因此,return *p
中的 *p
是未定义的行为,因为它访问内存的 struct X
从未存储到左值中,因此不再存在。然而,下面的示例 2 和 3 解决了这个问题并且没有表现出未定义的行为,并且是有效的。
示例 1(来自问题;是未定义的行为):
因此,这是不合法的:
int *p = get_x(argc+50).arr;
return *p;
查看 clang 11.0.1 LLVM C++ 编译器输出的这些警告:https://godbolt.org/z/PajThdsxz :
<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
使用 clang 11.0.1 C 编译器 时,不存在此类警告:https://godbolt.org/z/Y3zdszMvG。不知道为什么。
示例 2(正常):
但这很好:
int p = get_x(argc+50).arr[0];
return p;
示例 3(正常):
...这也很好:
struct X x = get_x(argc+50);
int *p = x.arr;
return *p;
有趣的是,上述所有 3 个版本生成的编译程序集 完全相同(仅当在 C++ 中编译时),表明虽然第一个可能未定义,但在用 C++ 编译时,对于这个特定的编译器,它与其他两个 一样工作。这是 C++ 程序集输出:
get_x(int): # @get_x(int)
mov eax, edi
ret
main: # @main
push rax
add edi, 50
call get_x(int)
pop rcx
ret
但是,C 编译器生成的程序集 在所有 3 种情况下都不同,并且明显更长 而不是 C++ 编译器生成的程序集。请参阅上面的最后一个 godbolt link 亲眼看看。
看起来 clang C++ 编译器比 clang C 编译器聪明得多。
以这段有争议的代码为例。
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
return一个struct X
。
我只对它的成员 arr
感兴趣。如果我只想要 arr
...
但是..该代码正确吗?
在所示示例中,C 标准是否知道将 get_x
的 return 值保留在堆栈中,直到调用堆栈帧结束,因为我正在使用指针?
标准不允许您做的事情。
从函数中 returned 的结构具有 临时生命周期,它在使用它的表达式之外结束。所以在 p
初始化之后,它指向一个生命周期结束的对象,它的值变为indeterminate。然后尝试在以下语句中取消引用 p
(现在不确定)触发 undefined behavior.
这在 C standard 的第 6.2.4p8 节中有记录:
A non-lvalue expression with structure or union type, where the structure or union contains a member with array type (including, recursively, members of all contained structures and unions) refers to an object with automatic storage duration and temporary lifetime. Its lifetime begins when the expression is evaluated and its initial value is the value of the expression.
Its lifetime ends when the evaluation of the containing full expression or full declarator ends. Any attempt to modify an object with temporary lifetime results in undefined behavior.
对象的生命周期以及指向对象的指针在其生命周期结束时发生的情况在第 6.2.4p2 节中指定:
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. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime
如果您要将函数的 return 值分配给 struct X
的实例,那么您可以安全地访问该实例的 arr
成员。
In the shown example, does the C standard know to keep the return value of get_x on the stack until the end of the calling stack frame because I'm peeking inside it with a pointer?
不,它永远不能这样做,即使它“知道”这样做。当一个函数 returns 时,事情会从堆栈中弹出,并且该点“上方”的任何内容变得未定义。
即便如此,
But.. is that code correct?
That part is! 这是因为您没有创建指向被调用者堆栈帧 中的结构的指针。您正在创建一个指向 副本 的指针,它是在您 return 按值编辑结构时隐式创建的。
从概念上讲,代码会将此结构复制到调用者堆栈帧中保留的 space(因为您专门调用了一个 return 结构的函数,在一般情况下,该值可以'被return编入寄存器)。实际上,优化编译器可能 return 它在一个寄存器中(如果你的机器的寄存器可以容纳一个包含 int 和 float 的结构),直接在调用者的堆栈框架中构造它(正确的位置很容易发现作为从被调用者堆栈框架的基址的偏移量),随机播放内存(破坏性的重叠移动操作是可以接受的,正是因为“内存内容现在未定义”的事情),等等
...但正如@dbush 所指出的那样,只有那一部分。要正确创建一个副本(即,有足够长的生命周期来使用这种方式),函数中的 return 值需要是一个左值。从概念上讲,一旦完成检索 .arr
成员,编译器就可以将该副本从堆栈中弹出。实际上,堆栈指针不会得到调整,但优化编译器会认为堆栈的那部分可以免费用于其他局部变量。
OP 目标与代码不同。
I'm only interested in its member
arr
. Why would I make a local variable for the entire struct if I only wantarr
... (?)
成员.arr
是一个数组。所以int *p = get_x(argc+50).arr;
并没有复制数组.arr
,而是将.arr[0]
的地址复制到p
中。复制地址不满足“只对其成员感兴趣arr
”。如果你想要.arr
中的数据,复制数据。
要仅复制数组 .arr
而不是整个 struct X
,请使用 memcpy()
.
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];
Does the C standard keep the return struct of a function on the stack if I keep a pointer to a value inside it?
不,如果结构是非左值,则不是,这意味着您不是在从函数返回后将其存储到变量中。
In the shown example, does the C standard know to keep the return value of get_x on the stack until the end of the calling stack frame because I'm peeking inside it with a pointer?
没有。阅读
问题不在于 get_x()
函数——没关系。相反,在原始问题和下面的示例 1 中的错误代码中,问题只是按值返回 struct X
(由 get_x()
返回)不是左值(分配给一个变量),所以它是短暂的,这意味着它的存储持续时间在评估 int *p = get_x(argc+50).arr;
行后结束。因此,return *p
中的 *p
是未定义的行为,因为它访问内存的 struct X
从未存储到左值中,因此不再存在。然而,下面的示例 2 和 3 解决了这个问题并且没有表现出未定义的行为,并且是有效的。
示例 1(来自问题;是未定义的行为):
因此,这是不合法的:
int *p = get_x(argc+50).arr;
return *p;
查看 clang 11.0.1 LLVM C++ 编译器输出的这些警告:https://godbolt.org/z/PajThdsxz :
<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
使用 clang 11.0.1 C 编译器 时,不存在此类警告:https://godbolt.org/z/Y3zdszMvG。不知道为什么。
示例 2(正常):
但这很好:
int p = get_x(argc+50).arr[0];
return p;
示例 3(正常):
...这也很好:
struct X x = get_x(argc+50);
int *p = x.arr;
return *p;
有趣的是,上述所有 3 个版本生成的编译程序集 完全相同(仅当在 C++ 中编译时),表明虽然第一个可能未定义,但在用 C++ 编译时,对于这个特定的编译器,它与其他两个 一样工作。这是 C++ 程序集输出:
get_x(int): # @get_x(int)
mov eax, edi
ret
main: # @main
push rax
add edi, 50
call get_x(int)
pop rcx
ret
但是,C 编译器生成的程序集 在所有 3 种情况下都不同,并且明显更长 而不是 C++ 编译器生成的程序集。请参阅上面的最后一个 godbolt link 亲眼看看。
看起来 clang C++ 编译器比 clang C 编译器聪明得多。