没有数组的结构的生命周期

Lifetime of a structure with no arrays in it

取此代码:

#include <stdio.h>

struct S { int x; int y; };

struct S foo(int a, int b) {
    struct S s = { a, b };
    return s;
}

int main() {
    int a;

    a = foo(2, 4).x;
    printf("%d\n", a);
    return 0;
}

它按预期工作。我关心的是 returned 结构对象的生命周期。我知道标准讨论了包含数组的结构的临时生命周期,但在这种情况下,结构中没有数组。

所以我猜foo()一结束,它的return值应该就死了,对吧?但显然我们仍然可以访问 x 成员。为什么?

调用foo(2, 4) returns copy 函数内部的变量s

这个返回的(和临时的)副本的生命周期为完整表达式的末尾,即赋值 a = foo(2, 4).x

这意味着对 a 的赋值是在临时结构的生命周期结束之前完成的,这意味着您显示的代码是有效的。

您可以阅读更多关于生命周期的信息,例如this reference.

"I know the standard talks about temporary lifetime for structs that contain arrays, but in this case there is no array in the structure."

你是说这一段:

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.36) 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 ends. Any attempt to modify an object with temporary lifetime results in undefined behavior. An object with temporary lifetime behaves as if it were declared with the type of its value for the purposes of effective type. Such an object need not have a unique address.

36) The address of such an object is taken implicitly when an array member is accessed.

Source: ISO/IEC 9899:2018 (C18), §6.2.4/8

临时生命周期 是在包含数组成员的结构和联合的上下文中明确发明的,因为自 array to pointer decay 以来,通过名称访问数组成员会给你指向数组第一个元素的指针,在 C11 中添加本段之前,它调用了早期 C 标准中的未定义行为。

Chris Dodd 解释得更好一些 here

因此 临时生命周期(如标准中的含义)与具有 non-array 成员的结构无关。

"So I guess that as soon as foo() has ended, its return value should be dead, right?"

没有。 foo() return 是 struct S 对象的副本,而不是对本地 struct S 对象的引用。请注意,foo() 的 return 类型是 struct S,而不是 struct S *(指向 struct S 的指针)。

"But apparently we can still access the x member. Why?"

因为你return一份给来电者。您试图访问此副本的成员 x 而不是 foo().

内的 struct S 对象 s

来自 C 标准

4 A full expression is an expression that is not part of another expression or of a declarator. Each of the following is a full expression: an initializer that is not part of a compound literal; the expression in an expression statement; the controlling expression of a selection statement (if or switch); the controlling expression of a while or do statement; each of the (optional) expressions of a for statement; the (optional) expression in a return statement. There is a sequence point between the evaluation of a full expression and the evaluation of the next full expression to be evaluated.

当包含完整表达式的计算结束时,临时对象的生命周期结束。

所以在这个表达式语句中

a = foo(2, 4).x;

返回的结构类型对象是存活的,其数据成员x的值被赋值给在main中声明的变量a,具有main的块作用域。

foo 编辑的结构 return 只是一个值(也称为右值)。它不是对象,没有任何生命周期。

考虑一个函数 int foo(void) { return 3; }。这个 return 的 int 值为 3,我们不希望像 printf("%p", (void *) &foo()); 那样获取它的地址。 3 只是计算机中使用的一个值,没有关联存储。

类似地,给定 struct S { int x, y; }struct S foo(void) { return (struct S) { 3, 4 }; } returns 一个包含 3 和 4 的 struct S 值。虽然我们经常将结构视为内存布局,但 C 标准将此 return 值视为一个值。它是一个复合值,有多个部分,但它只是一个没有关联存储的值。它不是 C 模型中的对象。

同样,给定 struct S { int x, y[1]; }struct S foo(void) { return (struct S) { 3, { 4 } }; } returns 一个包含 3 的 struct S 值和一个包含 4 的数组。这里 C 标准把自己画成了一个角落。它希望支持来自函数的 returning 结构,但是,当您访问数组时,如 foo().y[0] 中,当前 C 2018 6.3.2.1 3 中的规则表示数组被转换为指向其数组的指针第一个元素。指针必须指向存储,因此必须有一些对象指向。我想一种解决方案可能是说您不能在此类结构值中单独使用数组。 (您可以通过使用 struct S x = foo(); 将其复制到对象中然后使用 x 来使用 return 值。)但是,C 委员会采用的解决方案是为此类结构定义临时生命周期.他们在 C 2018 6.2.4 8 中对此的定义仅为包含数组成员的结构和联合定义临时生命周期。

但是,在您的代码中,这不是问题。因为您的 foo(2, 4) return 是一个值,所以您可以根据需要使用该值; foo(2, 4).x 有效,因为它采用值的 x 成员。它不需要担心任何对象的生命周期,因为不涉及任何对象。