总结
对C标准的潜在违反在于C 2018 6.8.5 3中的这句话:
for 语句的声明部分只能声明具有存储类 auto 或 register 的对象的标识符。
由于struct { int i; float f; } 既声明了类型又声明了标识符,因此对于如何解释 6.8.5 3 存在一些问题。在我看来:
- 委员会很可能打算禁止声明任何内容,但
auto 或 register 对象的标识符。
- 可能没有考虑这种偶然声明类型的用例。
- 允许这种附带声明将是无害的,并且不会明显偏离意图。
(我会邀请任何更熟悉 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 语句的声明部分只能声明具有存储类 auto 或 register 的对象的标识符。
就英语语法和用法而言,这句话可能有多种含义,包括:
- 声明应声明的唯一内容是具有存储类
auto 或register 的对象的标识符。
- 声明应声明的唯一标识符是具有存储类
auto 或register 的对象的标识符。
- 声明应声明的对象的唯一标识符是具有存储类
auto 或register 的对象的标识符。
首先,问题是“唯一”与它正在修改的东西不相邻。 “唯一”可能是修改“标识符”或“对象”或“存储类”。人们可能更喜欢修饰语来修饰最接近它的候选词,但句子的作者并不总是这样构造它们。 (语法上,它也可以修饰“有”,从而将对象限定为只有存储类auto 或register 而没有其他任何东西,例如没有大小或其他属性。我们很容易在语义上排除这个含义而不是语法依据。)
这些示例说明了含义之间的差异:
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 循环之外不需要技术上声明的类型。