Here's a simple function delcared and defined using old style syntax:
#include <stdio.h>
void
error(message,a1,a2,a3,a4,a5,a6,a7)
char *message;
char *a1,*a2,*a3,*a4,*a5,*a6,*a7;
{
fprintf(stderr,message,a1,a2,a3,a4,a5,a6,a7);
}
int main ()
{
error("[ERROR %d]: %s.\n",110,"Connection timed out");
return 0;
}
It can be compiled and runs correctly to print:
[ERROR 110]: Connection timed out.
I read that this style doesn't have associated prototype, but how can it convert int to char * automatically at runtime and even the provided arguments are fewer than it's declared?
Basically, it works because it's too dumb to know better. Old fashioned K&R C basically doesn't check anything. You get away with this because,
it happens that
sizeof(int) == sizeof(char *)
on the particular architecture and compiler combination you're using. It's not really converting anything, it just figures 32 bits is 32 bits.When you put all those arguments on the stack, it just pushed them in. When printf uses them, it just uses the ones if needs and leaves the rest alone; they then disappear when the call returns, and no one's the wiser. However, should you ever happen to try printing seven values where you only passed six arguments, it'll blow up at run time, sometime in creative and unexpected ways.
Actually, the compiler effectively does have a prototype in scope if it encounters the definition of the function
error()
before encountering its use (this is why old C programmers often order function definitions in files according to their order of use). Consequently, the110
can get converted to(char*)110
(not that it would matter on a machine wheresizeof(int) == sizeof(char*)
). It would be interesting to see what would happen on a machine wheresizeof(int) != sizeof(char*)
.Passing too few arguments, or the wrong type (you've done both), causes undefined behavior. This is exactly why you should never use old style syntax in new code. If you used new syntax, you would get a "free" prototype from the function definition. In other words:
is also a prototype.
Using old syntax, you have to provide your own, which you haven't. That means the compiler can't check the calls.
In practice (on your machine),
error
is reading theint
from the stack into achar *
. Then, it passes thechar *
tofprintf
. But a%d
specifier is used, sofprintf
pops it as anint
. This is more undefined behavior. But it happens to work on your machine;char *
andint
are likely the same size.error
also reads 5 garbagechar *
values off the stack. It then passes these tofprintf
, which it ignores because there are only two conversion specifiers.Actually there is a conversion of 110 to int, this conversion is done by fprintf, when fprint reads "%d", it tries to convert the corresponding parameter to int. Another point is that the function expect pointers, i.e, memory address, and pointers are integers. If a string had been passed instead of 110, there would still be a number printed, the address of the string at run-time.