Search code examples
cgccx86armclang

What is exactly undefined when using a C function forwardly declarated without its arguments?


While fiddling about old strange compatibility behaviors of C, I ended up with this piece of code:

#include <stdio.h>
int f();
int m() {
    return f();
}
int f(int a) {
    return a;
}
int main() {
    f(2);
    printf("%i\n", m());
}

I'm sure that the call to f() in m() is an undefined behavior as f() should take exactly one argument, but:

  • on x86, both GCC 9.1 and clang 8.0.1 do not show any warning (nor in -Wextra, -Weverything or whatever) except when using GCC and -O3. The output is then 2 without -O3, 0 with it. On Windows, MSVC doesn't print any error and the program outputs just random numbers.
  • on ARM (Raspberry Pi 3), GCC 6.3.0 and clang 3.8.1, I observe the same behavior for errors, the option -O3 still outputs 0, but normal compilation leads to 2 with GCC and... 66688 with clang.

When the error message is present, it's pretty much what you would expect: (pretty funny as a is not present in the printed line)

foo.c: In function ‘m’:
foo.c:4:9: warning: ‘a’ is used uninitialized in this function [-Wuninitialized]
  return f();
         ^~~
foo.c: In function ‘main’:
foo.c:11:2: warning: ‘a’ is used uninitialized in this function [-Wuninitialized]
  printf("%i\n", m());

My guess is that -O3 leads GCC to inline the calls, thus making it understand that a problem occurs; and that the leftovers on the stack or in the registers are used as if they were the argument to the call. But how can it still compile? Is this really the (un)expected behavior?


Solution

  • The specific rule violated is C 2018 6.5.2.2 (Function calls) 6:

    If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined.…

    Since this is not a constraint, the compiler is not required to produce a diagnostic—the behavior is entirely undefined by the C standard, meaning the standard imposes no requirements at all.

    Since the standard imposes no requirements, both ignoring the issue (or failing to recognize it) and diagnosing a problem are permissible.