7

I am using the following flags (where cc is either gcc 4.2 or clang 8.0):

$ cc -Wall -Werror -pedantic -ansi -std=c89 main.c

(I know the -ansi flag is a bit redundant in this case)

The following gives me the expected error

main.c:31:8: warning: ISO C90 forbids mixing declarations and code [-Wdeclaration-after-statement]
  vec3 abc = {0};
int main()
{
  vec3 a = {0};
  vec3 b = {0};

  Vec3(2, 2, 2);

  vec3 abc = {0}; // Declared after a function call

  return 0;
}

However, the following does not

int main()
{
  vec3 a = Vec3(0, 1, 2);
  vec3 b = Vec3(0, 1, 2);

  vec3 abc = {0}; // Declared after a function call

  return 0;
}

Surely initializing a variable with a function still counts as mixing declarations and code?

The Vec3 function is very basic; no inline flag set, etc.

vec3 Vec3(float x, float y, float z)
{
  vec3 rtn = {0};

  rtn.x = x;
  rtn.y = y;
  rtn.z = z;

  return rtn;
}
Deduplicator
  • 41,806
  • 6
  • 61
  • 104
  • 2
    There is no making sense of this rule, it was removed from the language for a reason. – Lundin Nov 26 '19 at 12:45
  • @Lundin: If an implementation restricted automatic-object initializers to compile-time constant expressions, that could simplify single-shot compilers. The other thing the rule served to do is forbid branching to a position before an automatic object's declaration within that object's lifetime, which creates some weird corner cases. – supercat Nov 27 '19 at 17:20
  • @supercat Regarding single-shot compilers, C++ removed the rule in early 80s so it cannot have been much of an issue even back in the days. Regarding branching, the proper solution would have been to remove non-conditional branching upwards. Also, this is C90 compliant: `fail: { int x = 5; goto fail; }`. And lets not even mention setjmp. – Lundin Nov 28 '19 at 07:45
  • @Lundin: C++ was always designed to require multi-pass compilation. Branching before the block wherein an object isn't declared doesn't create problematic corner cases because the branch ends the object lifetime. Given something like `void test(short i=0; loop: i++; int j=i; if (j < 10) goto loop; }`, does the stored value of `j` change during its lifetime? Does that imply that the stored was accessed in a fashion that changed it? Was any lvalue expression of type `int` used for the purpose of modifying `j`? Note that `setjmp` is allowed to interact oddly with automatic-duration objects. – supercat Nov 30 '19 at 23:14

3 Answers3

9

In this code snippet

  vec3 a = Vec3(0, 1, 2);
  vec3 b = Vec3(0, 1, 2);

  vec3 abc = {0}; // Declared after a function call

there are only declarations. There are no statements. Function calls used to initialize the variables are expressions. They are not statements.

It seems this warning

warning: ISO C90 forbids mixing declarations and code

is confusing. It would be more correctly to write that

warning: ISO C90 forbids mixing declarations and statements

For example even a redundant semicolon introduces a null statement. So in general the compiler should issue a warning even for the following code snippet

  vec3 a = Vec3(0, 1, 2);;
                       ^^^^
  vec3 b = Vec3(0, 1, 2);
Vlad from Moscow
  • 224,104
  • 15
  • 141
  • 268
  • Oh right. I assumed calling a function as an initializer still counted as a statement. It seems even something like this is valid then: vec3 a = Vec3((1 == 1 ? 0 : 0), 1, 2); – Karsten Pedersen Nov 26 '19 at 12:08
  • 1
    @KarstenPedersen There are used the conditional operator and the comma operator that is expressions. – Vlad from Moscow Nov 26 '19 at 12:12
  • @VladfromMoscow: there's no comma operator in `vec3 a = Vec3((1 == 1 ? 0 : 0), 1, 2);` — there are only commas used to separate function arguments (which are not comma operators; there's no mandated left-to-right evaluation order when evaluating arguments to a function, whereas with a comma operator, the LHS is fully evaluated before the RHS is evaluated). – Jonathan Leffler Nov 26 '19 at 21:38
  • @JonathanLeffler You are right. I was not attentive. – Vlad from Moscow Nov 26 '19 at 21:40
5

The second function has three consecutive variable definitions with initializers — that isn't a problem.

What C90 (C89) does not allow is a declaration after a statement — within a given statement block (between { and }), the declarations must all precede any statements (non-declarations). A plain function call, not part of an initializer, is a statement.

That's why the GCC option for reporting on the problem is -Wdeclaration-after-statement.

Jonathan Leffler
  • 666,971
  • 126
  • 813
  • 1,185
3

You're misunderstanding the constraint. We can have declarations with initializers; the first non-declaration statement marks the end of the declarations, and after that point we're not allowed more declarations in that scope.

Non-declaration statements can be expression-statements (as above), compound statements (such as if or while) or blocks.

Toby Speight
  • 23,550
  • 47
  • 57
  • 84