使用-std=c11
和clang-902.0.39.2的Apple LLVM 9.1.0接受:
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错误?
答案 0 :(得分:2)
C11:
6.8.5.3 for语句
1语句
for ( clause-1 ; expression-2 ; expression-3 ) statement表现如下:表达式expression-2是控制表达式,它是 在每次执行循环主体之前进行评估。表达式expression-3是 每次执行循环主体后,将其评估为void表达式。如果第1条是 声明,它声明的任何标识符的范围是声明的其余部分和整个循环,包括其他两个表达式;在第一次对控制表达式求值之前按执行顺序达到它。如果子句1是一个表达式,则在对控制表达式进行第一次求值之前,它会被作为void表达式求值。158)
剩下的[2],只是谈到处理省略的子句/表达式...
158)因此,第1节指定了循环的初始化,可能声明了一个或多个变量供循环使用;控制表达式expression-2指定在每次迭代之前进行的求值,以便继续执行循环,直到表达式比较等于0为止;而expression-3指定在每次迭代后执行的操作(例如递增)。
没有提及任何限制。因此,作为第二个示例,是一个有效的声明–并且我假设clang也不会抱怨以下内容:
void f(void)
{
struct { int x, y; } point;
// use point...
}
–那么第二个示例应该是有效的C代码,据我所知,clang拒绝是错误的。
答案 1 :(得分:0)
C 2018 6.8.5 3中的这句话可能导致违反C标准:
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,struct { int i; float f; }
是一个结构或联合说明符,其中的int i; float f;
是一个结构声明列表。因此,此源代码与6.7.2.1 8的描述匹配,因此声明了一个类型。
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
语句的声明部分中只能存在一个声明,但是有时用多个不同类型的对象来管理循环很有用。 li>
for
循环的具有自动存储生成的对象。for
循环之外不需要技术上声明的类型。