Calling a function with the wrong number (or types) of arguments is an error. The standard requires that an implementation detect some, but not all, of them.
That the standard calls the implementation is usually a compiler with a separate linker (and some other things), where the compiler translates the single translation units (i.e. the preprocessed source file) into object files that are later combined together, although the standard is not different, its authors, of course, wrote it with a typical setting.
C11 (n1570) 6.5.2.2 "Functional Calls", p2:
If the expression denoting the called function is of a type that includes the prototype, the number of arguments must match the number of parameters. Each argument must be of a type so that its value can be assigned to an object with an unqualified version of the type of its corresponding parameter.
This is in the โrestrictionsโ section, which means that the implementation (in this case, the compiler) must complain and can interrupt the translation if the โrequiredโ requirement is violated.
In your case, the prototype was visible, so the arguments to the function call must match the prototype.
Similar requirements apply to defining a function with a prototype declaration in an area; if your function definition does not match the prototype, your compiler should tell you. In other words, until you make sure that all function calls and the definition of this function are within the same prototype, you are informed if there is a mismatch. This can be achieved if the prototype is in the header file, which is included with all files with calls to this function and a file containing its definition. For this reason, we use prototype header files.
In the code shown, this check circumvents by providing a mismatch prototype and does not include the file2.h header.
In the same place. P9:
If a function is defined with a type that is incompatible with the type (of the expression) that the expression that indicates the function to call points to, the behavior is undefined.
Undefined behavior means that the compiler can assume that this is not happening and is not required to detect what it is doing.
And actually on my machine the generated object files from file2.c (I inserted return 0; to have some function body), it does not differ if I delete one of the function arguments, which means that the object file does not contain any information about arguments, and therefore the compiler only sees file2.o and file1.c . chance to detect a violation.
You mentioned overloading, so give compilation file2.c (with two and three arguments) as C ++ and look at the object files:
$ g++ file2_three_args.cpp -c $ g++ file2_two_args.cpp -c $ nm file2_three_args.o 00000000 T _Z3fooiii $ nm file2_two_args.o 00000000 T _Z3fooii
The foo function has its arguments included in the symbol created for it (a process called name change), the object file does contain some information about the types of functions. Accordingly, we get an error during the connection:
$ cat file1.cpp extern void foo(int x, int y); int main(void) { foo(1,2); } $ g++ file2_three_args.o file1.cpp In function `main': file1.cpp:(.text+0x19): undefined reference to `foo(int, int)' collect2: error: ld returned 1 exit status
This behavior will also be allowed to implement C, and interruption of the translation is a valid manifestation of undefined behavior at compile time or link time.
The way overloading in C ++ is usually done actually allows such checks during connection. This C does not have built-in support for function overloading and that undefined behavior for cases when the compiler cannot see type mismatches allows the generation of characters for functions without type information.