在内部声明结构吗?

时间:2018-11-25 13:58:39

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

使用-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错误?

2 个答案:

答案 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语句的声明部分应仅声明具有存储类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,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语句的声明部分中只能存在一个声明,但是有时用多个不同类型的对象来管理循环很有用。 li>
  • 它仍然是用于for循环的具有自动存储生成的对象。
  • for循环之外不需要技术上声明的类型。