【问题标题】:In C language, is it semantically possible to create an lvalue with incomplete type?在 C 语言中,在语义上是否可以创建类型不完整的左值?
【发布时间】:2017-09-04 01:59:46
【问题描述】:

在 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

【问题讨论】:

  • 什么例子?您已经提供了一个示例。不清楚你在问什么。注意您的问题措辞有误。 语法上是可能的,但语义上是不可能的。如果它在语法上是不可能的,你就会得到一个语法错误。
  • 要进行任何指针运算,编译器必须知道底层数据的大小。另一方面,将指针保留为变量不需要有关数据的知识。因此,您几乎可以将指针分配给不完整的数据类型,但仅此而已。
  • @EJP 你是对的,它应该是语义上的东西。我正在修复它。那么,如果它在语义上是不可能的,那么这条规则的意图是什么?
  • @EJP 一个“不完整类型的左值”的例子,它可以通过编译器的语义检查。或者规则的意图“如果左值的类型不完整并且没有数组类型,则行为未定义。”
  • 我认为 OP 的预期问题是:“是否有可能有一个没有违反约束的程序对不完整类型的左值执行左值转换?”

标签: c language-lawyer undefined-behavior lvalue incomplete-type


【解决方案1】:

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

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 in现行标准)。因此这是未定义的行为。

我在 gcc 和 clang 中尝试了我的程序,但他们都拒绝了它,错误是无法取消引用指向不完整类型的指针;但我在标准中找不到任何会违反约束的地方,所以我认为编译器拒绝该程序是不正确的。或者,由于省略了这样的约束,标准可能是有缺陷的,这是有道理的。

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

【讨论】:

  • 虽然我倾向于认为 C 标准应该禁止“取消引用指向不完整类型的指针”,但我没有找到它。也许你是对的。由于省略了这条规则,标准是有缺陷的。同样在这种情况下,UB 规则似乎是多余的。
  • 为什么你认为“编译失败”不是一种有效的未定义行为?
  • @immibis 编译器必须翻译程序,除非它在所有可能的代码路径中都有 UB。如果它不能翻译这个程序,那么它是不合格的,因为无法到达*s; 行(并且语言规则指定程序的其余部分必须工作)
  • @M.M:我想我希望标准将此称为“格式错误”,而不仅仅是 UB。如果不是这样,我真的很惊讶,我应该更多地研究标准。
  • @ChrisBeck“格式错误”是 C++ 的东西。在 C 中存在违反约束,这意味着编译器必须生成诊断并且可能拒绝翻译程序。还有一些未定义的行为不违反约束(例如,int x = 1 / argc; 当程序被argc == 0 调用时)。目前*s; 不是违反约束的,尽管我认为它可能应该是。
【解决方案2】:

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

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

...
use_foo(x);

无需编译器知道或关心即可成功处理 关于 struct foo 的实际表示[例如,某些系统可能会通过让调用者传递对象的地址并要求被调用函数在要修改它时制作副本来处理按值传递]。

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

【讨论】:

  • 我认为您对= y 部分是正确的;但是x = 是违反约束的(即使在 C89 中),因为赋值运算符有一个约束,即左侧必须是可修改的左值,并且“可修改的左值”的定义不包括不完整类型的左值。
  • 我看不出有任何方法可以在不包含违反约束的程序中使用= y 想法;因为赋值运算符约束包括如果右侧具有结构类型,则左侧必须具有兼容的结构类型
  • @M.M:按值传递不完整的结构类型怎么样?这会违反任何约束吗?此外,在许多地方,标准的某些部分做出了规定,但其他部分却使之成为不可能。该标准的作者并没有尝试修复主要是学究们感兴趣的无数缺陷,并且不会明显损害该标准的实用性。
  • 我想你就在那里:extern struct foo x; void f(); int main() { f(x); } 不会违反我能看到的任何限制。
  • @M.M:更重要的是,如果 f() 的合适声明存在于某处并且使用 argc!=3 调用程序,extern struct foo x; void f(struct foo); int main(int argc, char **argv) { if (argc==3) f(x); } 应该具有明确定义的行为。
【解决方案3】:

“未定义行为”术语包括编译错误作为选项。来自 C89 标准:

未定义的行为 - 在使用不可移植或错误的程序构造、错误数据或不确定值对象时的行为,标准对此没有任何要求。允许的未定义行为的范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),到终止翻译或执行(发出的诊断消息)。

如您所见,“终止翻译”是可以的。

在这种情况下,我相信您为示例代码得到的编译错误是作为编译时错误实现的“未定义行为”的示例。

【讨论】:

  • 啊哈!我明白了,所以“未定义的行为”不仅仅是运行时的不确定性。它基本上意味着“让编译器做他们想做的任何事情”。在这种情况下令人困惑。
  • 翻译不能终止,除非可以证明执行会到达未定义行为的部分
  • @M.M 你为什么这么认为?在一般情况下很难证明这样的事情。有没有参考标准?
  • 在一般情况下不可能证明,这就是为什么“未定义行为”的整个概念首先存在的原因。根据您的解释,编译器可能会拒绝编译代码if ( p == NULL ) { bar(); } else { p->foo(); },因为p->foo()p == NULL 时是未定义的行为。应用 UB 概念的唯一自洽方式是,如果它是由到达导致 UB 的表达式的执行流程触发的。 (但如果流动确实达到了这样的表达,效果可以“时间旅行”回来)。
  • 我认为标准的意图可能是允许编译器拒绝执行包含传递不完整类型参数的函数调用的程序,但我不确定符合标准的编译器在函数调用未实际执行的情况下可以这样做。
【解决方案4】:

当然,数组类型可以是:

extern double A[];
...
A[0] = 1;           // lvalue conversion of A

即使A 的定义对编译器不可见,它也具有明确定义的行为。所以在这个 TU 中,数组类型永远不会完成。

【讨论】:

  • 它没有显示在标题上,但我想问的是一个“具有不完整类型且没有数组类型”的左值,根据标准,它导致未定义行为。
  • 数组是“创建不完整类型的左值”的一个很好的例子。
猜你喜欢
  • 2011-09-24
  • 2011-05-29
  • 1970-01-01
  • 2016-05-04
  • 2022-10-13
  • 2012-03-25
  • 2014-01-31
  • 2020-09-09
  • 2015-07-20
相关资源
最近更新 更多