7

I know that function prototyping is mandatory in C++ if the function is defined after the main() function, but it is optional (but recommended) in C. I recently wrote a simple program that performs addition of 2 numbers, but by mistake used the dot operator instead of a comma when passing arguments.

#include <stdio.h>
int main()
{
    printf("sum is: %d",add(15.30)); // oops, uses dot instead of comma
}

int add(int a,int b)
{
    return a+b;
}

In the above program if the add(int,int) function is defined before the main() function then the program will surely fail to compile. This is because fewer parameters are passed when calling the function than what is required.

But, why does the above program compile and run fine - giving some large garbage value as an output? What is the reason? Is it better to use function prototyping so that the compiler detects the type mismatch and any other errors associated with function invocation?

Is this undefined behavior?

David Hoelzer
  • 15,862
  • 4
  • 48
  • 67
Destructor
  • 14,123
  • 11
  • 61
  • 126
  • 3
    It is undefined behavior, use warnings to prevent this kind of mistake. – Iharob Al Asimi May 28 '15 at 16:07
  • IIRC, before the introduction of varargs syntax, people would fake varargs by declaring a bunch of parameters and then just not passing some of them. It was never defined behavior, but people did it, and later compiler versions had to keep supporting the earlier code. – user2357112 May 28 '15 at 16:08
  • @user2357112: Are you sure it wasn't defined behavior in the days when parameters were listed between the closing parenthesis and the opening brace? I thought I'd seen in (1980s) Unix a few functions which used the first parameter to decide whether to examine later parameters, whose later parameters were sometimes omitted when unused. – supercat May 28 '15 at 16:31
  • @supercat Early C functions did not even require a "first parameter", thus `foo()` was allowed. How `int foo()` determined its parameters types and count was entirely up to the function definition (e.g. it could use a global variable for argument signature determination). – chux - Reinstate Monica May 28 '15 at 16:35
  • @supercat: I don't *think* it was, but I wasn't around back then, and I don't have access to first-edition K&R or whatever else might have defined it. – user2357112 May 28 '15 at 16:37

3 Answers3

10

In the earliest days of C, when methods looked like:

int add(a,b)
  int a;
  int b;
{
  return a+b;
}

given int x; a statement add(3,5); would generate code like:

push 5
push 3
call _add
store r0,_x

The compiler would not need to know anything about the add method beyond the return type (which it could guess as int) in order to generate the above code; if the method turned out to exist, that would be discovered at link time. Code which supplied more parameters than a routine expected would push those values on the stack where they would be ignored until the code popped them some time later.

Problems would arise if code passed too few parameters, parameters of the wrong types, or both. If code passed a fewer parameters than a routine was expecting, this would often have no effect if the called code didn't happen to use the later parameters. If code read parameters that weren't passed, they would generally read 'garbage' values (though there's no guarantee that the attempt to read those variables wouldn't have other side-effects). On many platforms, the first non-passed parameter would be the return address, though not always. If code wrote to parameters that weren't passed, that would generally mess up the return stack and lead to weird goofy behavior.

Passing incorrect-type parameters would often cause the called function to look in the wrong place for its arguments; if the passed types were smaller than what was expected, that could cause the called method to access stack variables it didn't own (as with the case of passing too few parameters).

ANSI C standardized the use of function prototypes; if the compiler sees a definition or declaration like int add(int a, int b) before it sees a call to add, then it will ensure that it passes two arguments of type int, even if the caller supplies something else (e.g. if the caller passes a double, its value will be coerced to type int before the call). An attempt to pass too few parameters to a method the compiler has seen will result in an error.

In the code above, the call to add is made before the compiler has any clue what the method is expecting. While newer dialects would not allow such usage, older ones would have the compiler assume by default that add is a method that will take whatever arguments the caller supplies and return int. In the above case, the called code would likely be expecting two words to be pushed on the stack, but the double constant would probably be pushed as two or four words. Thus, the method would probably receive for a and b two words from the binary representation of the double value 15.3.

BTW, even compilers which do accept the indicated syntax will almost always squawk if a function whose type was implicitly assumed is declared as returning something other than int; many will also squawk if such a function is defined using anything other than old-style syntax. While the first C compilers always pushed function arguments on the stack, newer ones generally expect them in registers. Thus, if one were to declare

 /* Old-style declaration for function that accepts whatever it's given
    and returns double */

double add();

then the code add(12.3,4.56) would push two double values on the stack and call the add function, but if one had instead declared:

double add(double a, double b);

then on many systems the code add(12.3,4.56) would load floating-point register 0 with 12.3 and floating-point register 1 with 4.56 before the call (not pushing anything). As a consequence, the two styles of declaration are not compatible. On such systems, attempting to call a method without declaring it, and subsequently declaring it within the same compilation unit using new-style syntax will yield a compilation error. Further, some such systems prefix the names of functions that take register arguments with two underscores and those that don't with one underscore, so if the method was defined using new-style syntax but called without the compiler having seen a prototype, the compiler would try to call _add even though the function was called __add. This would cause the linker to reject the program rather than generating an executable that wouldn't work.

supercat
  • 77,689
  • 9
  • 166
  • 211
9

If C sees you calling a function that hasn't been declared yet, it will generate an implicit declaration, taking any number of arguments, and assuming a return of int. In your example it is creating the implicit declaration of int add(); This is wrong, of course.

At runtime, the add function will receive a single double on its stack, and try to interpret the bytes in a confusing and undefined way.

C99 changed this rule and forbids calling undeclared functions, but most compilers still let you do it, albeit with a warning.

The printf is irrelevant.

Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • To expand upon this answer: if you were to either declare a prototype for your function, or simply define it before calling it in the file, you would get an appropriate error message. – larsks May 28 '15 at 16:10
  • "the add function will receive a single double on its stack, and try to interpret the bytes in a confusing and undefined way." Hmm, but what if `int add(int a,int b) { if (a==15) return 0; return a+b;}`, would this be OK? – chux - Reinstate Monica May 28 '15 at 16:25
  • Without a prototype, are you certain it is an implicit declaration of `int add(double)` and not `int add()`? – chux - Reinstate Monica May 28 '15 at 16:27
  • @chux that wouldn't be ok with the implicit declaration because the conversion from double to int wouldn't happen – Ryan Haining May 28 '15 at 16:36
0

why it is allowed to pass insufficient number of parameters when calling a function in C?

It is not.

The fact that your compiler doesn't warn/error out doesn't mean that it's correct.

  • 2
    This is completely valid in C90 and causes and implicit declaration (which unfortunately is incompatible with the later definition) A good compiler should warn however. – johannes May 28 '15 at 16:19
  • @johannes: Some compilers would generate identical code when passing two `int` values to methods declared as `int add()` or `int add(int,int)`; such compilers may regard the later definition as be compatible with the earlier one. – supercat May 28 '15 at 16:45
  • @johannes **you are wrong.** Calling a function with the wrong number of parameters is **undefined behavior** even in C90. **Read the question.** It's not about not specifying the parameter list (which was unfortunately valid in C90), but about calling the function with not enough arguments. Just see the quote: "**pass** insufficient number of parameters" – it says "pass" and not "declare". – The Paramagnetic Croissant May 28 '15 at 18:04
  • @TheParamagneticCroissant Mind the order! At the call in line 4 the function is not declared. Therefore the compiler is allowed (required) to "guess" there and assume it is a function taking a double and returning int. The story is different if there were a forward declaration or main() and add() were reordered. This is valid code triggering undefined behavior (calling a (implicit) wrongly declared function) And it is quite important to understand this behavior as this also happens when missing includes ... good that C99 removed that "feature". – johannes May 28 '15 at 18:14
  • @johannes this doesn't change the fact that you are **not allowed** to call a function with the wrong number of arguments. The fact that the code compiles doesn't mean that it's "allowed". Just like you *can* get around the type system by modifying a `const` object through a pointer-to-non-`const`, and it will compile. But it's still explicitly prohibited. (also your "valid code triggering UB" argument is complete nonsense. If the code triggers UB, it's definitely not valid. "compiles" != "valid".) – The Paramagnetic Croissant May 28 '15 at 18:16
  • I agree that the program is incorrect. For a standards compliant C90 compiler however there is no issue. (A good compiler will warn, but the issue is not the wrong number of parameters, but the incorrect implicit declaration the compiler creates from its limited information in that location) – johannes May 28 '15 at 18:21
  • @johannes it can actually be both. The difference is semantic… the problem is not the implicit declaration nor the call in itself. The problem is that the two mismatch. But even that is irrelevant, since my answer is correct either way: all I stated was that one is not allowed to call a function with the wrong number of arguments. That's definitely a **correct** statement. Is this really why you downvoted? Unbelievable. – The Paramagnetic Croissant May 28 '15 at 18:24
  • Prior to C89, there were implementations where a called function was expected to remove any arguments placed on the stack by the caller; popping the wrong number of arguments would typically result in stack corruption. There were other implementations where the caller was responsible for cleaning up the stack, any extra arguments would be ignored, and passing fewer arguments than declared would have no ill effect [it would actually yield more efficient code] provided that code only tried to use the arguments that were passed. The C Standard mandates a design compromise, allowing... – supercat Jul 11 '16 at 14:42
  • ...non-variadic functions to be implemented in callee-cleanup fashion (so the caller must pass the exact parameters expected or badness will result), but the "..." part of a variadic function must be implemented in caller-cleanup fashion. The Standard doesn't disallow implementations which implement all old-style functions in caller-cleanup fashion from documenting that behavior and guaranteeing correct operation with more or fewer arguments--it merely refrains from mandating that implementations offer such guarantees. – supercat Jul 11 '16 at 14:46