关于 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);
}
我的问题:
- 从基础开始,我希望
x1
、x2
、p1
和p2
无可争议地是左值,y.a->b
的修改在这个例子中是合法的。正确吗?
- 但是
.a
成员在 x
和 p
情况下呢?取消引用时它是左值吗?
- 关于p6 and p7,
.a
成员是否有变长数组类型?如果是,是基于声明还是初始化?
- 因此,
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?
是的。
But what about the .a member in the x and p cases? Is it an lvalue when dereferenced?
是的。 x1.a
、x2.a
、p1->a
和 p2->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?
是的,绝对。
我正在努力寻找另一个可能与 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);
}
我的问题:
- 从基础开始,我希望
x1
、x2
、p1
和p2
无可争议地是左值,y.a->b
的修改在这个例子中是合法的。正确吗? - 但是
.a
成员在x
和p
情况下呢?取消引用时它是左值吗? - 关于p6 and p7,
.a
成员是否有变长数组类型?如果是,是基于声明还是初始化? - 因此,
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?
是的。
But what about the .a member in the x and p cases? Is it an lvalue when dereferenced?
是的。 x1.a
、x2.a
、p1->a
和 p2->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?
是的,绝对。