Eric Postpischil
Eric Postpischil

Reputation: 223747

Declare structure within for?

Apple LLVM 9.1.0 with clang-902.0.39.2, using -std=c11, accepts:

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

but rejects:

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

with the message:

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

Is Clang correct to reject this, because it violates some constraint in the C standard? Which clause and paragraph? Or is it a Clang bug?

Upvotes: 4

Views: 217

Answers (2)

Eric Postpischil
Eric Postpischil

Reputation: 223747

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.)

The Declaration Declares a Type, Structure Member Identifiers, and an Object Identifier

Consider the declaration in this for statement:

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

It declares:

  • A type, the untagged structure.
  • Identifiers i and f for the members of the structure.
  • The object s.

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 and 2, permitted by 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 a structure 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, nor are the names of its members.

Upvotes: 2

Aconcagua
Aconcagua

Reputation: 25536

C11:

6.8.5.3 The for statement

1 The statement

for ( clause-1 ; expression-2 ; expression-3 ) statement
behaves as follows: The expression expression-2 is the controlling expression that is evaluated before each execution of the loop body. The expression expression-3 is evaluated as a void expression after each execution of the loop body. If clause-1 is a declaration, the scope of any identifiers it declares is the remainder of the declaration and the entire loop, including the other two expressions; it is reached in the order of execution before the first evaluation of the controlling expression. If clause-1 is an expression, it is evaluated as a void expression before the first evaluation of the controlling expression.158)

Left out [2], just speaking of handling ommited clauses/expressions...

158) Thus, clause-1 specifies initialization for the loop, possibly declaring one or more variables for use in the loop; the controlling expression, expression-2, specifies an evaluation made before each iteration, such that execution of the loop continues until the expression compares equal to 0; and expression-3 specifies an operation (such as incrementing) that is performed after each iteration.

Nothing mentioned about any restrictions. So as second example is a valid declaration – and I assume clang does not complain about the following either:

void f(void)
{
    struct { int x, y; } point;
    // use point...
}

– then the second example should be valid C code, so as far as I see, clang is wrong in rejecting.

Upvotes: 3

Related Questions