【问题标题】:Declare structure within for?在 for 中声明结构?
【发布时间】:2018-11-25 13:58:39
【问题描述】:

带有 clang-902.0.39.2 的 Apple LLVM 9.1.0,使用 -std=c11,接受:

typedef struct { int i; float f; } S;
for (S s = { 0, 0 }; s.i < 25; ++s.i, s.f = i/10.f)
    …

但拒绝:

for (struct { int i; float f; } s = { 0, 0 }; s.i < 25; ++s.i, s.f = s.i/10.f)
    …

带有消息:

错误:在'for'循环中声明非局部变量

Clang 拒绝这个是否正确,因为它违反了 C 标准中的某些约束?哪个条款和段落?还是 Clang 的 bug?

【问题讨论】:

  • 我重新打开了这个,因为我起草了一个新的答案,通过更多的分析来探索这种情况,并且因为我认为the previous original 中接受的答案是不正确的。

标签: c for-loop struct clang language-lawyer


【解决方案1】:

C11:

6.8.5.3 for 语句

1 声明

for (clause-1 ; expression-2 ; expression-3 ) 语句
行为如下:表达式 expression-2 是控制表达式,它是 在每次执行循环体之前进行评估。表达式表达式 3 是 在每次执行循环体后评估为 void 表达式。如果第 1 条是 声明,它声明的任何标识符的范围是声明的其余部分和整个循环,包括其他两个表达式;它在控制表达式的第一次评估之前按照执行顺序到达。如果 Clause-1 是一个表达式,它在控制表达式的第一次评估之前被评估为一个 void 表达式。158)

省略[2],只说处理省略的子句/表达式...

158) 因此,clause-1 指定循环的初始化,可能声明一个或多个变量用于循环;控制表达式 expression-2 指定在每次迭代之前进行的评估,这样循环的执行将继续,直到表达式比较等于 0;而 expression-3 指定每次迭代后执行的操作(如递增)。

没有提到任何限制。所以第二个例子是一个有效的声明——我假设 clang 也没有抱怨以下内容:

void f(void)
{
    struct { int x, y; } point;
    // use point...
}

--那么第二个例子应该是有效的C代码,所以据我所知,clang拒绝是错误的。

【讨论】:

  • 标准中的其他地方可能有限制,我怀疑有。考虑for (static int i = 0;…)。我们知道这不是 for 中的声明的用途,因此在 C 标准的其他地方有一个限制是有意义的,即在 for 中声明的东西必须具有自动存储持续时间,例如。如果我们发现这一点,我们可能会看到它的措辞是否也排除在 for 中声明新的结构类型(Clang 是正确的)(Clang 是错误的——开发人员误解了限制?)。
  • @EricPostpischil 好点。但我也查看了该标准的后续章节,虽然被承认,但不是太彻底,因为它们涉及完全不同的主题。附件 A.2.3.6.8.5 又回来了,但也没有进一步的信息(无论如何它只是提供信息)。该索引仅引用已说明的部分。
  • 看起来好像 for 循环初始化器中的静态变量实际上是合法的。有趣:C++17 有类似的措辞,但范围更广。 GCC 拒绝 C 的静态初始化程序,但不拒绝 C++!根据(各自的?)标准,不确定两者中的哪一个是正确的......
  • C11 §6.8.5 Iteration statements ¶3: for 语句的声明部分只能声明具有存储类autoregister 的对象的标识符。 那不在仅限for 部分,但在通用“迭代语句”部分中。
  • 我不确定为struct 的成员声明标识符是否意味着包含在“仅声明对象的标识符”中。由于结构在循环中声明时是无标记的,因此只有成员可能会违反“唯一”限制,但我并不是 100% 清楚结构类型是对象。标记结构可能会与它发生冲突——区分标记结构和无标记结构可能不是故意的。标记的结构类型仅在循环中可见(§6.8.5 ¶5)。在我看来,这就像标准中的模棱两可。
【解决方案2】:

总结

对C标准的潜在违反在于C 2018 6.8.5 3中的这句话:

for 语句的声明部分只能声明具有存储类 autoregister 的对象的标识符。

由于struct { int i; float f; } 既声明了类型又声明了标识符,因此对于如何解释 6.8.5 3 存在一些问题。在我看来:

  • 委员会很可能打算禁止声明任何内容,但 autoregister 对象的标识符。
  • 可能没有考虑这种偶然声明类型的用例。
  • 允许这种附带声明将是无害的,并且不会明显偏离意图。

(我会邀请任何更熟悉 C 委员会记录的人提请我们注意与此相关的任何事情。)

(我在这个答案中引用了 2018 C 标准,但语言 是旧的并且存在于以前的版本中,可能有一些不同的编号 条款或段落。)

类型和标识符都被声明

以下for 语句中的声明同时声明了标识符s 和未命名的类型:

for (struct { int i; float f; } s = { 0, 0 }; s.i < 25; ++s.i, s.f = s.i/10.f)
    …

我们知道它声明了一个类型,因为 C 2018 6.7.2.1 8 说:

struct-or-union-specifier 中的 struct-declaration-list 的存在声明了翻译单元内的新类型。

根据 6.7.2.1 1,struct { int i; float f; } 是一个结构或联合说明符,在其中,int i; float f; 是一个结构声明列表。所以这个源码符合6.7.2.1 8的描述,所以声明了一个类型。

C 标准的语言不明确

C 2018 6.8.5 3 说:

for 语句的声明部分只能声明具有存储类 autoregister 的对象的标识符。

就英语语法和用法而言,这句话可能有多种含义,包括:

  1. 声明应声明的唯一内容是具有存储类autoregister 的对象的标识符。
  2. 声明应声明的唯一标识符是具有存储类autoregister 的对象的标识符。
  3. 声明应声明的对象的唯一标识符是具有存储类autoregister 的对象的标识符。

首先,问题是“唯一”与它正在修改的东西不相邻。 “唯一”可能是修改“标识符”或“对象”或“存储类”。人们可能更喜欢修饰语来修饰最接近它的候选词,但句子的作者并不总是这样构造它们。 (语法上,它也可以修饰“有”,从而将对象限定为只有存储类autoregister 而没有其他任何东西,例如没有大小或其他属性。我们很容易在语义上排除这个含义而不是语法依据。)

这些示例说明了含义之间的差异:

static int s                 // Prohibited by 1, 2, and 3.
extern int s(int)            // Prohibited by 1 and 2, permitted by 3.
struct { int i; float f; } s // Prohibited by 1, permitted by 2 and 3.
int s                        // Permitted by 1, 2, and 3.

效果可以阐明意图

基于实现 C 的困难,似乎没有理由偏爱这些含义中的任何一个。要看到这一点,请考虑 C 实现可能很容易重写:

for (declaration; …; …) …

到等效代码:

{ declaration; for (; …; …) … }

因此,如果 C 实现可以支持声明和一般的 for 语句,那么它可以支持 for 语句中的一般声明,而无需付出额外的努力。

那么 6.8.5 3 的目的是什么?

for 语句中的声明提供了便利。它提供了一种声明某些迭代器或其他用于控制循环的对象的好方法,同时将范围限制为for 语句(这是避免错误的好处)。它不提供任何新功能。鉴于此,我希望编写 6.8.5 3 的目的是使声明能够服务于此目的,而不会将其开放给其他目的。在for 语句中使用上述前两个示例声明中的任何一个,虽然并非不可能,但会很奇怪。

如果是这样,我怀疑委员会的表面意图是 1,但他们没有考虑偶然声明未命名类型的情况。当我们使用结构反思第三个示例时,我们发现它很不寻常,但与for 语句的习惯用法并没有太大的不同:

  • 它自然而然地解决了for 语句的声明部分中可能只存在一个声明的问题,但有时管理具有不同类型的多个对象的循环很有用。
  • 它仍然是具有自动存储生成功能的对象,用于for 循环。
  • for 循环之外不需要技术上声明的类型。

【讨论】:

    猜你喜欢
    • 2021-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多