8

在 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 循环中声明匿名结构,尽管我必须使宏的使用不那么直观。我无法将声明外包给树的其余部分,因为它使用可变长度数组。

4

5 回答 5

6

我恭敬地不相信以前的答案。它使用 gcc (with -Wall -pedantic) 成功构建,但不能使用 clang 或 Visual Studio。

Microsoft 在此Microsoft Connect 错误项中承认了与 Visual Studio 极为相似的问题。

6.8.5 是说for-init-expression中的标识符声明不能是typedef, externor (除了andstatic之外的唯一存储类说明符)。autoregister

C99 中的存储类说明符auto是默认的并且是隐式的。然后结构类型和标识符i是自动的(在该代码块内具有范围)。那么代码肯定是有效的吗?我看不到 6.8.5 是如何禁止声明类型的。

我建议gcc是正确的,这是clang和Visual Studio实现的一个错误。

于 2012-08-11T08:53:07.417 回答
5

概括

C 标准的潜在违反在于 C 2018 6.8.5 3 中的这句话:

声明的声明部分for应仅声明具有存储类auto或的对象的标识符register

由于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 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

就英语语法和用法而言,这句话可能有多种含义,包括:

  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,但有时管理具有不同类型的多个对象的循环很有用。
  • 它仍然是一个具有自动存储生成的对象,正如用于for循环的那样。
  • for循环外不需要技术上声明的类型。
于 2012-08-10T14:23:12.070 回答
3

在 C99 中是不允许的。§6.8.5 说:

3 for 语句的声明部分应仅声明具有存储类autoregister.

您显示的声明除了声明 object 之外还声明了一个 type i,并且 type 不是 object 的标识符。

于 2012-08-10T14:22:06.783 回答
1

作为一种解决方法,您可以为每个恰好循环一次的结构成员添加外部 for 循环。它很丑,但至少从使用角度来看是一样的

于 2015-10-13T07:37:58.397 回答
0

对于一小段代码(https://github.com/kutoga/CArr),我需要类似的东西,并且您的问题/代码有一个简单的解决方案:

for (__auto_type i = ({
      struct {int foo; int bar;} _i = {0};
      _i;
    }); i.foo < 10; i.foo++)

解决方案是将匿名类型放入({...})并将其分配给__auto_type. 这适用于 gcc 和 clang。

于 2021-09-30T19:11:20.320 回答