I just resolved an absolute headbanger of a problem, and the issue was so simple, yet so elusive. So frustratingly hidden behind a lack of compiler feedback and an excess of compiler complacency (which is rare!). During writing this post, I found a few similar questions, but none that quite match my scenario.
- Calling method without typeless argument produces a compiler error when the definition includes strongly typed args.
- Why does gcc allow arguments to be passed to a function defined to be with no arguments? and C function with incomplete declaration both pass excess arguments to an argumentless function.
- Why does an empty declaration work for definitions with int arguments but not for float arguments? does contain a successfully building declaration/definition mismatch, but has no invocation, where I would expect to see a
too few arguments
message.
I have a function declaration with no args, a call to that function with no args, and the function definition below with args. Somehow, C manages to successfully call the function, no warning, no error, but very undefined behaviour. Where does the function get the missing argument from? Why don't I get a linker error since the no-arg function isn't defined? Why don't I get a compiler error because I'm redefining a function with a different signature? Why, oh why, is this allowed?
Compiling as C++ code (gcc -x c++
, enabling Compile To Binary on Godbolt) I get a linker error as expected, because of course C++ allows overloading, and the no-arg overload isn't defined. By checking with Godbolt, compiling with Clang and MSVC as C code also both build successfully, with only MSVC spitting out a minor warning.
Here is my reduced example for Godbolt.
// Compile with GCC or Clang -x c -Wall -Wextra
// Compile with MSVC /Wall /W4 /Tc
#include <stdio.h>
#include <stdlib.h>
// This is just so Godbolt can do an MSVC build
#ifndef _MSC_VER
# include <unistd.h>
#else
# define read(file, output, count) (InputBuffer[count] = count, fd)
#endif
static char InputBuffer[16];
int ReadInput(); // <-- declared with no args
int main(void)
{
int count;
count = ReadInput(); // <-- called with no args
printf("%c", InputBuffer[0]); // just so the results, and hence the entire function call,
printf("%d", count); // don't get optimised away by not being used (even though I'm
return 0; // not using any optimisation... just being cautious)
};
int ReadInput(int fd) // <-- defined with args!
{
return read(fd, InputBuffer, 1); // arg is definitely used, it's not like it's optimised away!
};