在C语言中,在语义上是否可以创建一个不完整类型的左值?

sox*_*xhc 11 c lvalue undefined-behavior language-lawyer incomplete-type

在C89标准中,我发现了以下部分:

3.2.2.1左值和函数指示符

除非它是sizeof运算符的操作数,一元&运算符,++运算符, - 运算符或者左运算符.运算符或赋值运算符,没有数组类型的左值将转换为存储在指定对象中的值(并且不再是左值).如果左值具有限定类型,则该值具有左值类型的非限定版本; 否则该值具有左值的类型.如果左值具有不完整类型且没有数组类型,则行为未定义.

如果我正确读取它,它允许我们创建一个lvalue并在其上应用一些运算符,这些运算符在运行时编译并可能导致未定义的行为.

问题是,我想不出一个"不完整类型的左值"的例子,它可以传递编译器的语义检查和触发器undefined behavior.

考虑一个左值是

左值是表达式(与对象类型或大于空隙以外的不完全类型)指定的一个对象.

那个不完整的类型是

类型被划分为对象类型(描述对象的类型),函数类型(描述函数的类型)和不完整类型(描述对象但缺少确定其大小所需的信息的类型).

我试过一个失败的程序:

struct i_am_incomplete;
int main(void)
{
    struct i_am_incomplete *p;
    *(p + 1);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

并得到以下错误:

error: arithmetic on a pointer to an incomplete type 'struct i_am_incomplete'
    *(p + 1);
      ~ ^
Run Code Online (Sandbox Code Playgroud)

任何人都可以想到这方面的例子吗?"不完整类型的左值"的示例,它可以传递编译器的语义检查和触发器undefined behavior.


更新:

正如@algrid在答案中所说,我误解了undefined behavior,其中包含compile error了一个选项.

也许我是鸡蛋里挑骨头,我还在这里想知道的根本动机喜欢undefined behaviordisallowing an lvalue to have an incomplete type.

M.M*_*M.M 5

我相信这个程序演示了这个案例:

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; }
Run Code Online (Sandbox Code Playgroud)

在标记的行上,*s是一个不完整类型的左值,它不属于3.2.2.1引用中的任何"除外..."情况(当前标准中为6.3.2.1/2).因此它是未定义的行为.

我在gcc和clang中尝试了我的程序,并且他们都拒绝了它,错误是指向不完整类型的指针不能被取消引用; 但是我无法在标准中的任何地方找到违反约束的内容,因此我认为编译器拒绝该程序是不正确的.或者可能通过省略这样的约束来标准是有缺陷的,这是有意义的.

(由于代码在一个内部if(0),这意味着编译器不能仅仅因为它是未定义的行为而拒绝它).

  • @immibis编译器必须翻译程序,除非它在所有可能的代码路径中都有UB.如果它无法翻译这个程序它是不符合的,因为无法达到行`*s;`(并且语言规则指定程序的其余部分必须工作) (3认同)
  • @ChrisBeck"格式错误"是一个C++的东西.在C中存在*约束违规*,这意味着编译器必须生成诊断并且可能拒绝转换程序.还有一些不违反约束的未定义行为(例如`int x = 1/argc;`当使用`argc == 0`调用程序时).目前`*s;`不是约束违规,尽管我认为它应该是. (2认同)

sup*_*cat 2

某些构建系统的设计方式可能允许如下代码:

extern struct foo x;
extern use_foo(struct foo x); // Pass by value

...
use_foo(x);
Run Code Online (Sandbox Code Playgroud)

编译器不必知道或关心 struct foo 的实际表示即可成功处理[例如,某些系统可能通过让调用者传递对象的地址并要求被调用函数创建一个如果要修改的话请复制]。

这样的设施可能对支持它的系统很有用,我认为标准的作者不想暗示使用该功能的代码是“损坏的”,但他们也不想强制所有 C实现支持这样的功能。使行为未定义将允许实现在实际时支持它,而不需要它们这样做。