关于 C11 临时生命周期规则和未定义行为的更多问题

More questions on the C11 temporary lifetime rule and undefined behaviour

我正在努力寻找另一个可能与 C11 中的 temporary lifetime rule / ISO/IEC 9899:2018 相关的示例的明确答案,我在此再次引用它以方便搜索:

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.

首先,我不确定这条规则是否适用。例子是:

#include <stdio.h>
#include <assert.h>

struct A {
    int b;
};

struct X {
    int x;
    struct A *a;
};

static void
foo(const char *n, struct X *x)
{
    assert(x != NULL);
    assert(x->a != NULL);
    printf("%s = {{ .x = %d, .a[0] = { .b = %d }}}\n", n, x->x, x->a->b);
}

#define FOO(x) foo(#x, x)

void
main(void) {
    struct X x1 = { .x = 11, .a = &(struct A){ .b = 21 }};
    struct X x2 = { .x = 12, .a = (struct A [2]){{ .b = 22 }, { .b = 23 }}};
    FOO(&x1);
    FOO(&x2);

    /* UB? */
    x1.a->b = 31;
    FOO(&x1);
    x2.a[0].b = 32;
    FOO(&x2);

    /* --- */

    struct X *p1 = &(struct X){ .x = 31, .a = &(struct A){ .b = 41 }};
    struct X *p2 = &(struct X){ .x = 32, .a = (struct A [2]){{ .b = 42 }, { .b = 43 }}};

    FOO(p1);
    FOO(p2);

    /* UB? */
    p1->a->b = 51;
    FOO(p1);
    p2->a[0].b = 52;
    FOO(p2);

    /* --- */

    struct A a[2] = {{ .b = 2 }, { .b = 3 }};
    struct X y = { .x = 1, .a = a};

    FOO(&y);

    /* should be legal */
    y.a->b = 4;
    FOO(&y);
}

我的问题:

谢谢!

To start with the basics, I hope that x1, x2, p1 and p2 indisputably are lvalues and that the modification of y.a->b in the example is legal. Correct?

是的。

But what about the .a member in the x and p cases? Is it an lvalue when dereferenced?

是的。 x1.ax2.ap1->ap2->a 指向的对象不是临时对象。他们是 compound literals [6.5.2.5]。它们是可写的左值,它们的生命周期是从封闭块退出,就像一个普通的局部变量,具有 auto 存储持续时间。它们在您访问它们的每个点都处于活动状态。

您从 6.2.4p8 引用的段落与您的代码无关,该代码不包含任何具有临时生命周期的对象。临时值类似于 return 类型 return 类型 struct A 的函数。例如:

struct A blah(void) {
    struct A ret = { 47, NULL };
    return ret;
}

void other(void) {
    printf("%d\n", blah().x); // valid, prints 47
    blah().x = 15;            // error, blah().x not an lvalue
    int *ptr = &(blah().x);   // error, blah().x not an lvalue
}

blah() 编辑的对象 return 不是左值,但是当结构包含数组成员时,您可能会遇到获取此类对象地址的情况。这就是为什么存在关于生命周期和修改的语言。

struct B {
    int arr[5];
};

struct B foobar(void) {
    struct B ret = { { 0,1,2,3,4 } };
    return ret;
}

void other(void) {
    printf("%d\n", foobar().arr[3]); // valid, prints 3;
    foobar().arr[2] = 7;             // compiles but is UB, temporary may not be modified
    int *ptr = foobar().arr + 2;     // valid but useless
    // lifetime of object returned by `foobar()` ends
    printf("%d\n", *ptr);            // UB, lifetime has ended
}

With respect to p6 and p7, does the .a member have a variable length array type? If yes, is that based on the declaration or the initialization?

没有。 .a 成员只有一个指针类型,struct A *。结构成员不能具有可变长度数组类型;这将是 6.7.6p3 下的可变修改类型,并且对于结构或联合成员是禁止的;参见 6.7.2.1p9 和注释 123。

可变长度数组类似于:

void stuff(size_t n) {
    int vla[n];
}

(有一个稍微相关的概念灵活数组成员, 6.7.2.1p18,它实际上是结构末尾的数组成员,其大小是确定的由您动态分配的额外 space 数量。但这是一个单独的问题。)

And, consequently, is the behavior of modifications of x1.a->b, x2.a->b, p1->a->b and p2->a->b well defined?

是的,绝对。