0

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?

  • 1
    Have you tried `-Wstrict-prototypes`? Prototypeless declarations are obsolete misfeature of C language, and should never be used. There is really no point in trying to reason with behaviour, because it's not applicable to any correctly written real world codebase. – user694733 Sep 06 '19 at 12:48
  • That works indeed, and yells at me because I have declared all my functions without prototypes... Thanks! – Nicolas Derumigny Sep 06 '19 at 13:12
  • Considering C89 introduced prototypes it is a bit of a shame that ‘-Wstrict-prototypes’ is not a default 30 years on. – artless noise Sep 06 '19 at 15:55
  • For the general discussion on "what's the meaning of empty parentheses in function declaration and definition" [see this QA](https://stackoverflow.com/q/41803937/918959) – Antti Haapala -- Слава Україні Sep 06 '19 at 18:12

1 Answers1

6

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • That makes sense, but how can it not have a prototype even though I have forwarded declared it? What is the meaning of `int f();` (especially, why does it differs from `int f(void);`)? – Nicolas Derumigny Sep 06 '19 at 13:06
  • @NicolasDerumigny `int f()` prototype says `f` can take any number of unspecified parameters. `int f(void)` says that function `f` takes exactly no parameters. – Michael Petch Sep 06 '19 at 13:12
  • 1
    @NicolasDerumigny: A function prototype is a declaration with parameter types. In C, `int f()` is a declaration without a prototype; it leaves the parameter types flexible/unspecified for now. (This differs from C++, in which `int f()` says the function takes no parameters.) In C, `int f(void)` says the function takes no parameters, so it is different from `int f()`. – Eric Postpischil Sep 06 '19 at 13:48