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

发布于 2025-01-09 01:00:14 字数 2264 浏览 2 评论 0原文

我正在努力寻找可能与 相关的另一个示例的明确答案来自 C11 / ISO/IEC 9899:2018 的临时生命周期规则,我在这里再次引用该规则以方便搜索:

具有结构或联合类型的非左值表达式,其中结构或联合包含 数组类型的成员(递归地包括所有包含的结构和联合的成员) 指具有自动存储期限和临时生存期的对象。 36) 它的生命周期开始 当计算表达式时,其初始值是表达式的值。它的生命周期结束了 当包含的完整表达式的评估结束时。任何修改对象的尝试 临时生命周期会导致未定义的行为。具有临时生命周期的对象的行为就好像它 出于有效类型的目的,用其值的类型进行声明。这样的对象不需要 有一个唯一的地址。

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

#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);
}

我的问题:

  • 从基础开始,我希望 x1x2p1p2 毫无疑问是左值,并且示例中 ya->b 的修改是合法的。正确的?
  • 但是 xp 情况下的 .a 成员又如何呢?取消引用时它是左值吗?
  • 关于 p6p7.a 成员是否具有可变长度数组类型?如果是,是基于声明还是初始化?
  • 因此,修改 x1.a->bx2.a->bp1->a-> 的行为;bp2->a->b 定义良好吗?

谢谢你!

I am struggling to find a definitive answer on yet another example possibly concerning the temporary lifetime rule from C11 / ISO/IEC 9899:2018, which I am quoting here again to facilitate searches:

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.

To begin with, I am not sure if the rule even applies. The example is:

#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);
}

My questions:

  • 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?
  • 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?
  • And, consequently, is the behavior of modifications of x1.a->b, x2.a->b, p1->a->b and p2->a->b well defined?

Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

守望孤独 2025-01-16 01:00:14

从基础开始,我希望 x1、x2、p1 和 p2 无可争议地是左值,并且示例中 ya->b 的修改是合法的。对吗?

是的。

但是 x 和 p 情况下的 .a 成员呢?取消引用时它是左值吗?

是的。 x1.ax2.ap1->ap2->a 的对象> 指向的不是临时的。它们是复合文字 [6.5.2.5]。它们是可写的左值,它们的生命周期直到从封闭块退出为止,就像具有 auto 存储持续时间的普通局部变量一样。它们在您访问它们的每个点上都是活跃的。

您从 6.2.4p8 引用的段落与您的代码无关,该代码不包含任何具有临时生命周期的对象。临时值类似于返回类型为 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()返回的对象不是左值,但是当结构体包含数组成员时,您可能会遇到获取此类对象地址的情况。这就是为什么存在有关生命周期和修改的语言。

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
}

对于p6和p7,.a成员是否具有可变长度数组类型?如果是,是基于声明还是初始化?

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

可变长度数组类似于:(

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

灵活数组成员有一个稍微相关的概念,6.7.2.1p18,它实际上是位于结构的末尾,其大小由动态分配的附加空间量决定,但这是一个单独的问题。)

因此,x1.a->b、x2.a->b、p1->a->b 和 p2->a->b 的修改行为得到了明确定义?

是的,绝对是。

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?

Yes.

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

Yes. The objects which x1.a, x2.a, p1->a, and p2->a point to are not temporaries. They are compound literals [6.5.2.5]. They are lvalues that are writable, and their lifetime is until exit from the enclosing block, just like an ordinary local variable with auto storage duration. They are alive at every point where you access them.

The passage you quoted from 6.2.4p8 is irrelevant to your code, which does not contain any objects with temporary lifetime. A temporary would be something like the value returned by a function with return type struct A. For instance:

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
}

The object returned by blah() is not an lvalue, but you can get into a situation of taking the address of such an object when the struct contains an array member. This is why the language about lifetime and modification is there.

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?

No. The .a member simply has a pointer type, struct A *. A struct member cannot have a variable length array type; that would be a variably modified type under 6.7.6p3, and that is forbidden for a struct or union member; see 6.7.2.1p9 and note 123.

A variable length array is something like:

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

(There is the slightly related concept of a flexible array member, 6.7.2.1p18, which is effectively an array member at the end of a struct, whose size is determined by the amount of additional space you dynamically allocate. But that's a separate question.)

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

Yes, absolutely.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文