Here's a question that's pretty special and one I've never been aware of before. Apparently I can create a function in C that differs from its function prototype in the passing parameters. The only requirement is that the function must not know the function prototype at compile time.
In my minimal example, I created the following header Test.h:
int Test(int);
I also created the following main.c:
#include <stdio.h>
#include "Test.h"
int main()
{
int a = Test(5);
int b = Test(5);
printf("%d", a);
}
When I use this Test.c, an error occurs when compiling, as expected:
#include <stdio.h>
#include "Test.h"
void Test(void)
{
printf("HW\n");
}
If I write Test.c like this, the program is compiled, linked and is executable. Of course, the variable a has a
seemingly arbitrary value.
#include <stdio.h>
void Test(void)
{
printf("HW\n");
}
Why can this program be linked and where does the return parameter come from, for example? This also seems very dangerous if something like this happens accidentally in larger code.
Quick additional info, when I use the same code in a C++ project, there are Linker Errors. So if I use main.cpp and Test.cpp instead of main.c and Test.c. Here things seem to be handled differently with the left. But I would also have been surprised, since C++ has function overloading.
The behavior is undefined, as you have observed.
The reason you do not get an error is the linker has no information about the calling convention used in Test.c and main.c the only information is the symbol name Test
and the type of reference (function call).
Here is a possible sequence of events:
main
function calls the Test
function, which probably behaves as expected: it calls printf
which should print HW
and a newline, then it returns without a return value.main
then retrieves the return value it expects to have been stored in the conventional place where it should have been put by Test
, in general, for an int
return type, this is a register, and main
calls printf
to print this value.3
because printf
returned 3
as an int
, thus it stored 3 in the proper register, and Test
probably did not change the register between the printf
call and its own return statement (no guarantee of that).You can investigate more by making the compiler produce the assembly source code with -s
or -S
, or play with Godbolt Compiler Explorer
Compiling the code as C++ does produce an error because unlike C, C++ stores the argument types and class information along with the function names to handle overloading (it constructs synthetic names in a process called mangling) so when you link the main.o object file with the incorrect Test.o, the function names do not match and you get a missing symbol error.
Your example is a very good example of potential problems when linking multiple modules. The programmer(s) should adhere to good practice for mitigating this issue:
Modern compilers such as gcc and clang support warnings to to try and enforce this approach. You should always compile all source files with -Wall -Wextra -Werror
which define these:
-Wmissing-prototypes
(C and Objective-C only)
Warn if a global function is defined without a previous prototype declaration. This warning is issued even if the definition itself provides a prototype. Use this option to detect global functions that do not have a matching prototype declaration in a header file. This option is not valid for C++ because all function declarations provide prototypes and a non-matching declaration declares an overload rather than conflict with an earlier declaration. Use -Wmissing-declarations
to detect missing declarations in C++.
-Wmissing-variable-declarations
(C and Objective-C only)
Warn if a global variable is defined without a previous declaration. Use this option to detect global variables that do not have a matching extern declaration in a header file.
-Wmissing-declarations
Warn if a global function is defined without a previous declaration. Do so even if the definition itself provides a prototype. Use this option to detect global functions that are not declared in header files. In C, no warnings are issued for functions with previous non-prototype declarations; use -Wmissing-prototypes
to detect missing prototypes. In C++, no warnings are issued for function templates, or for inline functions, or for functions in anonymous namespaces.