在 C 语言中,是否可以在语义上创建类型不完整的左值?
In C language, is it semantically possible to create an lvalue with incomplete type?
在C89标准中,我找到了以下部分:
3.2.2.1 Lvalues and function designators
Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue). If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; otherwise the value has the type of the lvalue. If the lvalue has an incomplete type and does not have array type, the behavior is undefined.
如果我没看错的话,它允许我们创建一个 lvalue
并在其上应用一些运算符,这些运算符会编译并可能在运行时导致未定义的行为。
问题是,我想不出一个可以通过编译器语义检查并触发的“类型不完整的左值”的例子undefined behavior
。
考虑左值是
An lvalue is an expression (with an object type or an incomplete type other than void) that designates an object.
那个不完整的类型是
Types are partitioned into object types (types that describe objects), function types (types that describe functions), and incomplete types (types that describe objects but lack information needed to determine their sizes).
我试过一个失败的程序:
struct i_am_incomplete;
int main(void)
{
struct i_am_incomplete *p;
*(p + 1);
return 0;
}
并得到以下错误:
error: arithmetic on a pointer to an incomplete type 'struct i_am_incomplete'
*(p + 1);
~ ^
任何人都可以想出一个例子吗?可以通过编译器语义检查并触发的“类型不完整的左值”示例 undefined behavior
.
更新:
正如@algrid 在回答中所说,我误解了 undefined behavior
,其中包含 compile error
作为选项。
也许我在胡说八道,我仍然想知道这里的潜在动机是更喜欢 undefined behavior
而不是 disallowing an lvalue to have an incomplete type
。
我相信这个程序证明了这一点:
struct S;
struct S *s, *f();
int main(void)
{
s = f();
if ( 0 )
*s; // here
}
struct S { int x; };
struct S *f() { static struct S y; return &y; }
在标出的那一行,*s
是一个不完整类型的左值,它不属于你引用的3.2.2.1(即6.3.2.1)中的任何"Except..."情况/2 在当前标准中)。因此这是未定义的行为。
我在 gcc 和 clang 中尝试了我的程序,但它们都拒绝了它,错误是无法取消引用指向不完整类型的指针;但是我在标准中找不到任何违反约束的地方,所以我相信编译器拒绝该程序是不正确的。或者标准可能有缺陷,因为省略了这样的约束,这是有道理的。
(因为代码在 if(0)
中,这意味着编译器不能仅仅因为它是未定义的行为而拒绝它)。
"Undefined behavior" 术语包括编译错误选项。来自 C89 标准:
Undefined behavior - behavior, upon use of a nonportable or erroneous program construct, of erroneous data, or of indeterminately-valued objects, for which the Standard imposes no requirements. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
如您所见,"terminating a translation" 没问题。
在这种情况下,我相信您为示例代码得到的编译错误是 "undefined behavior" 作为编译时错误实现的示例。
当然,数组类型可以是:
extern double A[];
...
A[0] = 1; // lvalue conversion of A
这具有明确定义的行为,即使 A
的定义对编译器不可见。所以在这个 TU 中,数组类型永远不会完成。
某些构建系统的设计方式可能允许如下代码:
extern struct foo x;
extern use_foo(struct foo x); // Pass by value
...
use_foo(x);
无需编译器知道或关心即可成功处理
关于 struct foo 的实际表示[例如,某些系统可能通过让调用者传递对象的地址并要求被调用函数在要修改它时制作副本]来处理按值传递。
这样的功能在支持它的系统上可能会有用,我认为标准的作者并不想暗示使用该功能的代码是 "broken",但他们也没有不想强制所有 C 实现都支持这样的功能。使行为未定义将允许实现在可行时支持它,而不需要他们这样做。
在C89标准中,我找到了以下部分:
3.2.2.1 Lvalues and function designators
Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue). If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; otherwise the value has the type of the lvalue. If the lvalue has an incomplete type and does not have array type, the behavior is undefined.
如果我没看错的话,它允许我们创建一个 lvalue
并在其上应用一些运算符,这些运算符会编译并可能在运行时导致未定义的行为。
问题是,我想不出一个可以通过编译器语义检查并触发的“类型不完整的左值”的例子undefined behavior
。
考虑左值是
An lvalue is an expression (with an object type or an incomplete type other than void) that designates an object.
那个不完整的类型是
Types are partitioned into object types (types that describe objects), function types (types that describe functions), and incomplete types (types that describe objects but lack information needed to determine their sizes).
我试过一个失败的程序:
struct i_am_incomplete;
int main(void)
{
struct i_am_incomplete *p;
*(p + 1);
return 0;
}
并得到以下错误:
error: arithmetic on a pointer to an incomplete type 'struct i_am_incomplete'
*(p + 1);
~ ^
任何人都可以想出一个例子吗?可以通过编译器语义检查并触发的“类型不完整的左值”示例 undefined behavior
.
更新:
正如@algrid 在回答中所说,我误解了 undefined behavior
,其中包含 compile error
作为选项。
也许我在胡说八道,我仍然想知道这里的潜在动机是更喜欢 undefined behavior
而不是 disallowing an lvalue to have an incomplete type
。
我相信这个程序证明了这一点:
struct S;
struct S *s, *f();
int main(void)
{
s = f();
if ( 0 )
*s; // here
}
struct S { int x; };
struct S *f() { static struct S y; return &y; }
在标出的那一行,*s
是一个不完整类型的左值,它不属于你引用的3.2.2.1(即6.3.2.1)中的任何"Except..."情况/2 在当前标准中)。因此这是未定义的行为。
我在 gcc 和 clang 中尝试了我的程序,但它们都拒绝了它,错误是无法取消引用指向不完整类型的指针;但是我在标准中找不到任何违反约束的地方,所以我相信编译器拒绝该程序是不正确的。或者标准可能有缺陷,因为省略了这样的约束,这是有道理的。
(因为代码在 if(0)
中,这意味着编译器不能仅仅因为它是未定义的行为而拒绝它)。
"Undefined behavior" 术语包括编译错误选项。来自 C89 标准:
Undefined behavior - behavior, upon use of a nonportable or erroneous program construct, of erroneous data, or of indeterminately-valued objects, for which the Standard imposes no requirements. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
如您所见,"terminating a translation" 没问题。
在这种情况下,我相信您为示例代码得到的编译错误是 "undefined behavior" 作为编译时错误实现的示例。
当然,数组类型可以是:
extern double A[];
...
A[0] = 1; // lvalue conversion of A
这具有明确定义的行为,即使 A
的定义对编译器不可见。所以在这个 TU 中,数组类型永远不会完成。
某些构建系统的设计方式可能允许如下代码:
extern struct foo x;
extern use_foo(struct foo x); // Pass by value
...
use_foo(x);
无需编译器知道或关心即可成功处理 关于 struct foo 的实际表示[例如,某些系统可能通过让调用者传递对象的地址并要求被调用函数在要修改它时制作副本]来处理按值传递。
这样的功能在支持它的系统上可能会有用,我认为标准的作者并不想暗示使用该功能的代码是 "broken",但他们也没有不想强制所有 C 实现都支持这样的功能。使行为未定义将允许实现在可行时支持它,而不需要他们这样做。