在for循环中声明匿名结构的代码在gcc中使用-std = c99/gnu99正常工作
for (struct {int foo; int bar;} i = {0}; i.foo < 10; i.foo++);
Run Code Online (Sandbox Code Playgroud)
但是,当我切换到clang而不是我得到错误:
error: declaration of non-local variable in 'for' loop
Run Code Online (Sandbox Code Playgroud)
为什么这是一个错误?为什么它会允许某些类型(例如"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)) \
)
Run Code Online (Sandbox Code Playgroud)
这是我编写的一个非常方便的宏,它在堆栈上以深度优先顺序迭代AVL树.因为不允许在for循环中声明匿名结构,但我必须使宏不太直观.因为它使用可变长度数组,所以我无法将声明输出到树的其余部分.
可能违反C标准的是C 2018中的这句话6.8.5 3:
声明的声明部分
for只应声明具有存储类auto或对象的对象的标识符register.
由于struct { int i; float f; }声明了类型和标识符,因此有一些关于如何解释6.8.5的问题.在我看来:
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)
…
Run Code Online (Sandbox Code Playgroud)
我们知道它声明了一种类型,因为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.
Run Code Online (Sandbox Code Playgroud)
基于实现C的困难,似乎没有理由更喜欢这些含义.为了看到这一点,请考虑C实现可以轻松地重写:
for (declaration; …; …) …
Run Code Online (Sandbox Code Playgroud)
等效代码:
{ declaration; for (; …; …) … }
Run Code Online (Sandbox Code Playgroud)
因此,如果C实现一般可以支持声明和for语句,它可以在for语句中支持一般声明而无需额外的大量工作.
那么6.8.5 3的目的是什么?
声明中的for声明提供了便利.它提供了一种很好的方式来声明一些迭代器或用于控制循环的其他对象,同时将范围限制为for语句(这有助于避免错误).它不提供任何新功能.鉴于此,我预计6.8.5 3是为了使声明能够达到此目的而不打开其他目的.在for语句中使用上面的前两个样本声明中的任何一个都是奇怪的,尽管不是不可能的.
如果是这样,我怀疑委员会的表面意图是意义1,但他们没有考虑偶然宣布未命名类型的情况.当我们使用结构反思第三个样本时,我们发现这是不寻常的,但与惯常使用for语句不太一致:
for语句的声明部分中只有一个声明可能存在,但有时管理具有不同类型的多个对象的循环是有用的.for循环一样.for循环外不需要技术上声明的类型.以前的答案我很不相信.它使用gcc(with -Wall -pedantic)成功构建,但不能与clang或Visual Studio一起构建.
Microsoft在此Microsoft Connect错误项目中将Visual Studio认为是一个非常类似的问题.
6.8.5是说for-init-expression中的标识符声明不能typedef,extern或static(除了auto和之外的唯一存储类说明符register).
autoC99中的存储类说明符是默认值并且是隐式的.然后结构类型和标识符i是auto(在该代码块中具有范围).当然代码有效吗?我不知道6.8.5是如何禁止声明类型的.
我建议gcc是正确的,这是clang和Visual Studio实现的错误.