在for循环中声明匿名结构的代码在gcc中使用-std = c99 / gnu99正常工作
for (struct {int foo; int bar;} i = {0}; i.foo < 10; i.foo++);
然而,当我切换到clang时,我得到了错误:
error: declaration of non-local variable in 'for' loop
为什么这是一个错误?为什么它会允许某些类型(例如“int”)而不允许其他类型(例如struct {int foo;})?这似乎不一致。 clang是否无法正确实现c99或者代码无效c99和gcc恰好支持它?
有没有人知道在clang支持的for循环中声明多种类型变量的方法? (这对宏非常有用。)
编辑:
由于人们问为什么这有用,我会粘贴一些示例代码:
#define TREE_EACH(head, node, field, iterator) for ( \
/* initialize */ \
struct { \
node* cur; \
node* stack[((head)->th_root == 0? 0: (head)->th_root->field.avl_height) + 1]; \
uint32_t stack_size; \
} iterator = {.cur = (head)->th_root, .stack_size = 0}; \
/* while */ \
iterator.cur != 0; \
/* iterate */ \
(iterator.stack_size += (iterator.cur->field.avl_right != 0) \
? (iterator.stack[iterator.stack_size] = avl_right, 1) \
: 0), \
(iterator.cur = (iterator.cur->field.avl_left == 0) \
? iterator.cur->field.avl_left \
: (iterator.stack_size > 0? (iterator.stack_size--, iterator.stack[iterator.stack_size]): 0)) \
)
这是我编写的一个非常方便的宏,它在堆栈上以深度优先顺序迭代AVL树。因为不允许在for循环中声明匿名结构,但我必须使宏不太直观。我无法将声明输出到树的其余部分,因为它使用了一个可变长度的数组。
答案 0 :(得分:5)
可能违反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; }
是一个struct-or-union-specifier,在其中,int i; float f;
是一个struct-declaration列表。所以这个源代码与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
语句的声明部分中只有一个声明可能存在,但有时管理具有不同类型的多个对象的循环是有用的。for
循环一样。for
循环之外不需要技术声明的类型。答案 1 :(得分:5)
我对之前的答案表示不相信。它使用gcc(使用-Wall -pedantic
)成功构建,但不能使用clang或Visual Studio。
微软已经在此Microsoft Connect bug item承认了与Visual Studio非常类似的问题。
6.8.5表示 for-init-expression 中的标识符声明不能是typedef
,extern
或static
(唯一的存储类)除auto
和register
之外的说明符。
C99中的存储类说明符auto
是默认值并且是隐式的。然后,结构类型和标识符i
是自动的(在该代码块中具有范围)。当然代码有效吗?我不知道6.8.5是如何禁止声明类型的。
我建议gcc是正确的,这是clang和Visual Studio实现的错误。
答案 2 :(得分:3)
C99不允许这样做。 §6.8.5说:
3 for语句的声明部分只能声明 具有存储类
auto
或register
的对象的标识符。
您展示的声明除了声明对象i
之外还声明了一个类型,而且类型不是对象的标识符。
答案 3 :(得分:1)
作为一种解决方法,您可以为每个只循环一次的结构成员添加外部for循环。它很难看,但至少从使用角度看它会是相同的