8
votes

Code declaring anonymous structs in a for loop worked fine in gcc with -std=c99/gnu99

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

However when I switch to clang instead I got the error:

error: declaration of non-local variable in 'for' loop

Why is this an error? Why would it allow some types (e.g. "int") but not others (e.g. struct {int foo;}) ? This seems inconsistent. Does clang fail to implement c99 correctly or is that code invalid c99 and gcc just happens to support it?

Does anyone know of a way to declare more than one type of variable in a for loop that is supported by clang? (This is useful for macros.)

EDIT:

Since people asked why this is useful I will paste some example code:

#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)) \
)

This is a really convenient macro that I wrote which iterates over an AVL tree in depth-first order on the stack. Since declaring anonymous structs in the for loop is not allowed though I have to make the macro less intuitive to use. I could not possible out-source the declaration to the rest of the tree since it uses a variable length array.

4
Why do you need a struct as a local variable in a for loop? What purpose does it achieve?Jay
For the record VS 2008 also cannot compile that.acraig5075
Jay: It is useful in "foreach" style macro where the iterator is complex (requiring many different types) and anonymous as to not pollute the namespace. Declaring them above the for would essentially make declarations/identifiers leek out into the scope where the macro is used unless two blocks/end braces is used which is unintuitive.Hannes Landeholm
Is someone able to answer why it reportedly compiles with gcc?acraig5075

4 Answers

5
votes

I'm respectfully unconvinced by the previous answers. It builds successfully with gcc (with -Wall -pedantic), only not with clang nor Visual Studio.

Microsoft have acknowledged as a bug an extremely similar issue with Visual Studio at this Microsoft Connect bug item.

6.8.5 is saying that declarations of identifiers inside the for-init-expression cannot be typedef, extern or static (the only storage class specifiers other than auto and register).

The storage class specifier auto in C99 is default and is implicit. The struct type and identifier i are then auto (have scope within that code block). Surely the code is then valid? I don't see how 6.8.5 is forbidding the declaration of a type.

I suggest that gcc is correct, and it's a bug with the implementation by clang and Visual Studio.

5
votes

Summary

The potential violation of the C standard lies with this sentence in C 2018 6.8.5 3:

The declaration part of a for statement shall only declare identifiers for objects having storage class auto or register.

Since struct { int i; float f; } declares both a type and an identifier, there is some question about how to interpret 6.8.5 3. It appears to me that:

  • It is likely the committee intended to prohibit declaring anything but identifiers for auto or register objects.
  • This use case where a type is incidentally declared may not have been considered.
  • Allowing this incidental declaration would be harmless and not considerably out of line with the intent.

(I would invite anybody more familiar with C committee records to bring to our attention anything pertaining to this.)

(I give references to the 2018 C standard in this answer, but the language is old and exists in previous versions, perhaps with some different numbering of clauses or paragraphs.)

Both a Type and an Identifier Are Declared

The declaration in the following for statement declares both an identifier s and an unnamed type:

for (struct { int i; float f; } s = { 0, 0 }; s.i < 25; ++s.i, s.f = s.i/10.f)
    …

We know it declares a type because C 2018 6.7.2.1 8 says:

The presence of a struct-declaration-list in a struct-or-union-specifier declares a new type, within a translation unit.

Per 6.7.2.1 1, struct { int i; float f; } is a struct-or-union-specifier, and, within it, int i; float f; is a struct-declaration list. So this source code matches the description of 6.7.2.1 8, so it declares a type.

The Language of the C Standard is Ambiguous

C 2018 6.8.5 3 says:

The declaration part of a for statement shall only declare identifiers for objects having storage class auto or register.

As a matter of English grammar and use, several meanings are possible for this sentence, including:

  1. The only things the declaration shall declare are identifiers for objects having storage class auto or register.
  2. The only identifiers the declaration shall declare are identifiers for objects having storage class auto or register.
  3. The only identifiers for objects the declaration shall declare are identifiers for objects having storage class auto or register.

Primarily, the problem is the “only” is not adjacent to the thing it is modifying. The “only” could be modifying “identifiers” or “objects” or “storage class.” One might prefer the modifier to modify the candidate nearest it, but the authors of sentences do not always construct them thusly. (Grammatically, it could also modify “having,” thus qualifying the objects as having only storage class auto or register and not having anything else, such as not having size or other properties. We easily rule out this meaning on semantic rather than grammatical grounds.)

These samples illustrate differences between the meanings:

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.

Effects May Illuminate Intent

It does not appear there is a reason for preferring any of these meanings based on difficulties of implementing C. To see this, consider that a C implementation may easily rewrite:

for (declaration; …; …) …

to the equivalent code:

{ declaration; for (; …; …) … }

Thus, if a C implementation can support declarations and for statements in general, it can support general declarations in a for statement without significant additional effort.

What then is the purpose of 6.8.5 3?

The declaration in a for statement provides convenience. It provides a nice way of declaring some iterator or other object used to control the loop, while limiting the scope to the for statement (which is a benefit for avoiding bugs). It does not provide any new function. Given this, I expect 6.8.5 3 was written with the intent of enabling the declaration to serve this purpose without opening it up to other purposes. It would be odd, although not impossible, to use either of the first two sample declarations above in a for statement.

If so, I suspect the surface intent of the committee was meaning 1 but that they did not consider the situation where an unnamed type is incidentally declared. When we reflect on the third sample, using a structure, we see that it is unusual but is not too out of line with the customary use of a for statement:

  • It arises naturally as a solution to the problem that only one declaration may be present in the declaration part of a for statement, yet sometimes it is useful to manage the loop with multiple objects of different types.
  • It is still an object with automatic storage generation, as intended for for loops.
  • The type that is technically declared is not needed outside the for loop.
3
votes

It's not allowed in C99. §6.8.5 says:

3 The declaration part of a for statement shall only declare identifiers for objects having storage class auto or register.

The declaration you've shown declares a type in addition to the object i, and a type is not an identifier for an object.

1
votes

As a workaround you could add outer for-loop for each struct member that loops exactly once. It is ugly but at least from use perspective it will be the same