在C89标准中,我发现了以下部分:
3.2.2.1左值和函数指示符
除非它是sizeof运算符的操作数,一元&运算符,++运算符, - 运算符或者左运算符。运算符或赋值运算符,没有数组类型的左值被转换为存储在指定对象中的值(并且不再是左值)。如果左值具有限定类型,则该值具有左值类型的非限定版本;否则该值具有左值的类型。 如果左值具有不完整的类型且没有数组类型,则行为未定义。
如果我正确阅读它,它允许我们创建一个 lvalue
并在其上应用一些运算符,这些运算符在运行时编译并可能导致未定义的行为。
问题是,我想不出一个“不完整类型的左值”的例子,它可以传递编译器的语义检查和触发器 undefined behavior
。
考虑一个左值是
一个 左值 是指定对象的表达式(具有除void之外的对象类型或不完整类型)。
那个不完整的类型是
类型被划分为对象类型(描述对象的类型),函数类型(描述函数的类型)和 不完整的类型(描述对象但缺少确定其大小所需信息的类型)。
我尝试过失败的程序:
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
。
某些构建系统可能以某种方式设计,允许以下代码:
extern struct foo x;
extern use_foo(struct foo x); // Pass by value
...
use_foo(x);
在没有编译器必须知道或关心的情况下成功处理
关于struct foo的实际表示[例如,某些系统可以通过让调用者传递一个对象的地址来处理pass-by-value,并且如果它要修改它则要求被调用的函数进行复制]。
这样的设施在可以支持它的系统上可能是有用的,我不认为标准的作者想要暗示使用该功能的代码被“破坏”,但他们也不想强制要求所有C实现支持这样的功能。使行为未定义将允许实现在实际时支持它,而不要求它们这样做。
我相信这个程序演示了这个案例:
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/2)中的任何“除......”情况。因此它是未定义的行为。
我在gcc和clang中尝试了我的程序,他们都拒绝了它,错误是指向不完整类型的指针无法解除引用;但我无法在标准中找到任何会导致约束违规的地方,因此我认为编译器拒绝该程序是不正确的。或者可能通过省略这样的约束来使标准有缺陷,这是有意义的。
(因为代码在里面 if(0)
,这意味着编译器不能仅仅因为它是未定义的行为而拒绝它。
“未定义的行为”术语包括编译错误作为选项。从C89标准:
未定义的行为 - 在使用不可移植或错误的程序构造时,对错误数据或不确定值对象的行为,标准没有要求。允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行过程中以环境特征(有或没有发出诊断消息)的特定行为,终止翻译或执行(发布时)一条诊断信息)。
正如您所见,“终止翻译”是可以的。
在这种情况下,我相信您为您获得的编译错误示例代码是作为编译时错误实现的“未定义行为”的示例。
当然,数组类型可以是:
extern double A[];
...
A[0] = 1; // lvalue conversion of A
这有明确定义的行为,即使定义为 A
对编译器不可见。所以在这个TU中,数组类型永远不会完成。