With the following code:
int main(){
printf("%f\n",multiply(2));
return 0;
}
float multiply(float n){
return n * 2;
}
When I try to compile I get one warning: "'%f' expects 'double', but argument has type 'int'" and two errors: "conflicting types for 'multiply'", "previous implicit declaration of 'multiply' was here."
Question 1: I am guessing that it's because, given the compiler has no knowledge of function 'multiply' when he comes across it the first time, he will invent a prototype, and invented prototypes always assume 'int' is both returned and taken as parameter. So the invented prototype would be "int multiply(int)", and hence the errors. Is this correct?
Now, the previous code won't even compile. However, if I break the code in two files like this:
#file1.c
int main(){
printf("%f\n",multiply(2));
return 0;
}
#file2.c
float multiply(float n){
return n * 2;
}
and execute "gcc file1.c file2.c -o file" it will still give one warning (that printf is expecting double but is getting int) but the errors won't show up anymore and it will compile.
Question 2: How come when I break the code into 2 files it compiles?
Question 3: Once I run the program above (the version split into 2 files) the result is that 0.0000 is printed on the screen. How come? I am guessing the compiler again invented a prototype that doesn't match the function, but why is 0 printed? And if I change the printf("%f") to printf("%d") it prints a 1. Again, any explanation of what's going on behind the scenes?
Thanks a lot in advance.
So the invented prototype would be "int multiply(int)", and hence the errors. Is this correct?
Absolutely. This is done for backward compatibility with pre-ANSI C that lacked function prototypes, and everything declared without a type was implicitly int. The compiler compiles your main, creates an implicit definition of int multiply(int), but when it finds the real definition, it discovers the lie, and tells you about it.
How come when I break the code into 2 files it compiles?
The compiler never discovers the lie about the prototype, because it compiles one file at a time: it assumes that multiply takes an int, and returns an int in your main, and does not find any contradictions in multiply.c. Running this program produces undefined behavior, though.
Once I run the program above (the version split into 2 files) the result is that 0.0000 is printed on the screen.
That's the result of undefined behavior described above. The program will compile and link, but because the compiler thinks that multiply takes an int, it would never convert 2 to 2.0F, and multiply will never find out. Similarly, the incorrect value computed by doubling an int reinterpreted as a float inside your multiply function will be treated as an int again.
An unspecified function has a return type of int (that's why you get the warning, the compiler thinks it returns an integer) and an unknown number of unspecified arguments.
If you break up your project in multiple files, just declare a function prototype before you call the functions from the other files, and all will work fine.
Question1:
So the invented prototype would be "int multiply(int)", and hence the
errors. Is this correct?
Not exactelly yes because it it depends of your Cx (C89, C90, C99,...)
for function return values, prior to C99 it was explicitly specified that if no function declaration was visible the translator provided one. These implicit declarations defaulted to a return type of int
Justification from C Standard (6.2.5 page 506)
Prior to C90 there were no function prototypes. Developers expected to
be able to interchange argu-ments that had signed and unsigned
versions of the same integer type. Having to cast an argument, if the
parameter type in the function definition had a different signedness,
was seen as counter to C’s easy-going type-checking system and a
little intrusive. The introduction of prototypes did not completely do
away with the issue of interchangeability of arguments. The ellipsis
notation specifies that nothing is known about the 1590 ellipsis
supplies no information expected type of arguments. Similarly, for
function return values, prior to C99 it was explicitly specified that
if no function declaration was visible the translator provided one.
These implicit declarations defaulted to a return type of int . If the
actual function happened to return the type unsigned int , such a
default declaration might have returned an unexpected result. A lot of
developers had a casual attitude toward function declarations. The
rest of us have to live with the consequences of the Committee not
wanting to break all the source code they wrote. The
interchangeability of function return values is now a moot point,
because C99 requires that a function declaration be visible at the
point of call (a default declaration is no longer provided)
Question 2:
How come when I break the code into 2 files it compiles?
it will compile and it will be treated like indicated in the first question exactelly the same
Question 1: Yes you are correct. If there is no function prototype, the default type is int
Question 2: When you are compiling this code as one file, the compiler see that there is already function named multiply and it has a different type than supposed (double instead of int). Thus compilation doesn't work.
When you separate this in two files, the compiler makes two .o files. In the first one it suppose that the multiply() function will be in other file. Then linker links both files into a binary and according to the name multiply inserts call of float multiply() on the place of int multiply() supposed by the compiler in the first .o file.
Question 3: If you read int 2 as a float, you will get a very small number (~1/2^25), so after that you multiply it by 2 and it still remains too small for format %f. That's why you see 0.00000.
Related
I have code:
#include <stdio.h>
int main() {
int a = sum(1, 3);
return 0;
}
int sum(int a, int b, int c) {
printf("%d\n", c);
return a + b + c;
}
I know that I have to declare functions first, and only after that I can call them, but I want to understand what happends.
(Compiled by gcc v6.3.0)
I ignored implicit declaration of function warning and ran program several times, output was this:
1839551928
-2135227064
41523672
// And more strange numbers
I have 2 questions:
1) What do these numbers mean?
2) How function main knows how to call function sum without it declaration?
I'll assume that the code in your question is the code you're actually compiling and running:
int main() {
int a = sum(1, 3);
return 0;
}
int sum(int a, int b, int c) {
printf("%d\n", c);
return a + b + c;
}
The call to printf is invalid, since you don't have the required #include <stdio.h>. But that's not what you're asking about, so we'll ignore it. The question was edited to add the include directive.
In standard C, since the 1999 standard, calling a function (sum in this case) with no visible declaration is a constraint violation. That means that a diagnostic is required (but a conforming compiler can still successfully compile the program if it chooses to). Along with syntax errors, constraint violations are the closest C comes to saying that something is illegal. (Except for #error directives, which must cause a translation unit to be rejected.)
Prior to C99, C had an "implicit int" rule, which meant that if you call a function with no visible declaration an implicit declaration would be created. That declaration would be for a function with a return type of int, and with parameters of the (promoted) types of the arguments you passed. Your call sum(1, 3) would create an implicit declaration int sum(int, int), and generate a call as if the function were defined that way.
Since it isn't defined that way, the behavior is undefined. (Most likely the value of one of the parameters, perhaps the third, will be taken from some arbitrary register or memory location, but the standard says nothing about what the call will actually do.)
C99 (the 1999 edition of the ISO C standard) dropped the implicit int rule. If you compile your code with a conforming C99 or later compiler, the compiler is required to diagnose an error for the sum(1, 3) call. Many compilers, for backward compatibility with old code, will print a non-fatal warning and generate code that assumes the definition matches the implicit declaration. And many compilers are non-conforming by default, and might not even issue a warning. (BTW, if your compiler did print an error or warning message, it is tremendously helpful if you include it in your question.)
Your program is buggy. A conforming C compiler must at least warn you about it, and possibly reject it. If you run it in spite of the warning, the behavior is undefined.
This is undefined behavior per 6.5.2.2 Function calls, paragraph 9 of the C standard:
If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.
Functions without prototypes are allowed under 6.5.2.2 Function calls, paragraph 6:
If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. ...
Note again: if the parameters passed don't match the arguments expected, the behavior is undefined.
In strictly standard conforming C, if you don't declare a function before using it, it will assume certain default argument types for the function.This is based on early versions of C with a weaker type system, and retained only for backwards compatibility. It should not be used generally.
Ill skip the details here, but in your case it assumes sum takes 2 ints and returns an int.
Calling a function with the wrong number of parameters, as you are doing here, is undefined behaviour. When you call sum, the compiler thinks that it takes two integers, so it passes two integers to it. When the function is actually called, however, it tries to read one more integer, c. Since you only passed 2 ints, the space for c contains random crap, which is what you're seeing when you print out. Note that it doesn't have to do this, since this is undefined behaviour, it could do anything. It could have given values for b & c, for example.
Obviously this behaviour is confusing, and you should not rely on undefined behaviour, so you'd be better off compiling with stricter compiler settings so this program wouldn't compile. (The proper version would declare sum above main.)
1) Since you haven't provided value for parameter "c" when calling function "sum" its value inside the function is undefined. If you declared function before main, your program wouldn't even compile, and you would get "error: too few arguments to function call" error.
2) Normally, it doesn't. Function has to be declared before the call so the compiler can check function signature. Compiler optimizations solved this for you in this case.
I'm not 100% sure if C works exactly like this but, your function calls work like a stack in memory. When you call a function your arguments are put on that stack so when in the fuction you can access them by selecting less x positions on memory. So:
You call summ(1, 3)
the stack will have 1 and the on the top a 3.
when executing the fuction it will see the last position of memory for the 1º argument (it recovers the 1) and then the position before that for the 2º argument (recovering the 3), however, there is a 3º argument so it accesses the position before that as well.
This position is garbige as not put by you and different everytime you run it.
Hope it was clear enought. Remeber that the stack works is inverted so every time you add something it goes to the previous memory position, not the next.
With the following code:
int main(){
printf("%f\n",multiply(2));
return 0;
}
float multiply(float n){
return n * 2;
}
When I try to compile I get one warning: "'%f' expects 'double', but argument has type 'int'" and two errors: "conflicting types for 'multiply'", "previous implicit declaration of 'multiply' was here."
Question 1: I am guessing that it's because, given the compiler has no knowledge of function 'multiply' when he comes across it the first time, he will invent a prototype, and invented prototypes always assume 'int' is both returned and taken as parameter. So the invented prototype would be "int multiply(int)", and hence the errors. Is this correct?
Now, the previous code won't even compile. However, if I break the code in two files like this:
#file1.c
int main(){
printf("%f\n",multiply(2));
return 0;
}
#file2.c
float multiply(float n){
return n * 2;
}
and execute "gcc file1.c file2.c -o file" it will still give one warning (that printf is expecting double but is getting int) but the errors won't show up anymore and it will compile.
Question 2: How come when I break the code into 2 files it compiles?
Question 3: Once I run the program above (the version split into 2 files) the result is that 0.0000 is printed on the screen. How come? I am guessing the compiler again invented a prototype that doesn't match the function, but why is 0 printed? And if I change the printf("%f") to printf("%d") it prints a 1. Again, any explanation of what's going on behind the scenes?
Thanks a lot in advance.
So the invented prototype would be "int multiply(int)", and hence the errors. Is this correct?
Absolutely. This is done for backward compatibility with pre-ANSI C that lacked function prototypes, and everything declared without a type was implicitly int. The compiler compiles your main, creates an implicit definition of int multiply(int), but when it finds the real definition, it discovers the lie, and tells you about it.
How come when I break the code into 2 files it compiles?
The compiler never discovers the lie about the prototype, because it compiles one file at a time: it assumes that multiply takes an int, and returns an int in your main, and does not find any contradictions in multiply.c. Running this program produces undefined behavior, though.
Once I run the program above (the version split into 2 files) the result is that 0.0000 is printed on the screen.
That's the result of undefined behavior described above. The program will compile and link, but because the compiler thinks that multiply takes an int, it would never convert 2 to 2.0F, and multiply will never find out. Similarly, the incorrect value computed by doubling an int reinterpreted as a float inside your multiply function will be treated as an int again.
An unspecified function has a return type of int (that's why you get the warning, the compiler thinks it returns an integer) and an unknown number of unspecified arguments.
If you break up your project in multiple files, just declare a function prototype before you call the functions from the other files, and all will work fine.
Question1:
So the invented prototype would be "int multiply(int)", and hence the
errors. Is this correct?
Not exactelly yes because it it depends of your Cx (C89, C90, C99,...)
for function return values, prior to C99 it was explicitly specified that if no function declaration was visible the translator provided one. These implicit declarations defaulted to a return type of int
Justification from C Standard (6.2.5 page 506)
Prior to C90 there were no function prototypes. Developers expected to
be able to interchange argu-ments that had signed and unsigned
versions of the same integer type. Having to cast an argument, if the
parameter type in the function definition had a different signedness,
was seen as counter to C’s easy-going type-checking system and a
little intrusive. The introduction of prototypes did not completely do
away with the issue of interchangeability of arguments. The ellipsis
notation specifies that nothing is known about the 1590 ellipsis
supplies no information expected type of arguments. Similarly, for
function return values, prior to C99 it was explicitly specified that
if no function declaration was visible the translator provided one.
These implicit declarations defaulted to a return type of int . If the
actual function happened to return the type unsigned int , such a
default declaration might have returned an unexpected result. A lot of
developers had a casual attitude toward function declarations. The
rest of us have to live with the consequences of the Committee not
wanting to break all the source code they wrote. The
interchangeability of function return values is now a moot point,
because C99 requires that a function declaration be visible at the
point of call (a default declaration is no longer provided)
Question 2:
How come when I break the code into 2 files it compiles?
it will compile and it will be treated like indicated in the first question exactelly the same
Question 1: Yes you are correct. If there is no function prototype, the default type is int
Question 2: When you are compiling this code as one file, the compiler see that there is already function named multiply and it has a different type than supposed (double instead of int). Thus compilation doesn't work.
When you separate this in two files, the compiler makes two .o files. In the first one it suppose that the multiply() function will be in other file. Then linker links both files into a binary and according to the name multiply inserts call of float multiply() on the place of int multiply() supposed by the compiler in the first .o file.
Question 3: If you read int 2 as a float, you will get a very small number (~1/2^25), so after that you multiply it by 2 and it still remains too small for format %f. That's why you see 0.00000.
Here is the content of src1.c:
#include <stdio.h>
extern int w;
//int go(char); // no need to declare here. WHY????
main(){
char a='f';
go(a);
printf("%d\n", w);
}
And here is the content of src2.c:
#include <stdio.h>
int w = 99;
int go(char t){
printf("%c\n%d\n",t,sizeof(t));
}
Why isn't it mandatory to declare the go function in src1.c file after compiling it in Linux?
cc src1.c src2.c;
Does the compiler put the go function's definition from src2.c file above the main function's code so that declaration then would not be required?
In I do it this way:
#include <stdio.h>
int go(char); // need to declare here, because if not, arguments of go will be promoted to intS and they would conflict with char parameters defined in go. Error is droped!
main(){
char a='f';
go(a);
}
int go(char t){
printf("%c\n%d\n",t,sizeof(t));
}
So everyone that says, it is possible to pass whatever number and types of arguments in absence of prototype is wrong. They are promoted to ints in this case, but have to agree with those specified in definition.
I did some tests and found out that even it compiles with no errors it does not work correctly.
src1:
#include <stdio.h>
int go(int t){
printf("%d\n%d\n",t,sizeof(t));
}
sr2.c:
#include <stdio.h>
int go(int); //if I omit this prototype, program outputs 1 which is far from correct answer :)
main(){
double b=33453.834;
go(b);
}
So finally the answer could only be undefined behavior.
Thanks Maxim Skurydin
It's indeed not mandatory to have a prototype for a function before using it, but this is a quirk of the early days of C.
When there is no prototype present, the compiler cannot check the actual types that are being passed to the function or returned by it, which might be very bad in case of mismatch between the usage and declaration.
When compiler sees no prototype for go when go(b); is invoked, it assumes it has the following prototype int go(<any number of arguments can be there>). The default argument promotions are performed on the arguments prior to function invocation. Of course, if there is no function go in another translation module, you will get a linker error.
From c99 standard:
6.5.2.2 Function calls
If the expression that denotes the called function has a type that
does not include a prototype, the integer promotions are performed on
each argument, and arguments that have type float are promoted to
double. These are called the default argument promotions. If the
number of arguments does not equal the number of parameters, the
behavior is undefined. If the function is defined with a type that
includes a prototype, and either the prototype ends with an ellipsis
(, ...) or the types of the arguments after promotion are not
compatible with the types of the parameters, the behavior is
undefined. If the function is defined with a type that does not
include a prototype, and the types of the arguments after promotion
are not compatible with those of the parameters after promotion, the
behavior is undefined, except for the following cases:
— one promoted
type is a signed integer type, the other promoted type is the
corresponding unsigned integer type, and the value is representable in
both types;
— both types are pointers to qualified or unqualified
versions of a character type or void.
6.3.1.1 Boolean, characters, and integers
2/ If an int can represent all values of the original type, the value
is converted to an int; otherwise, it is converted to an unsigned int.
These are called the integer promotions.48) All other types are
update:
Does compiler put the go function's definition from src2.c file above
the main function's code so that declaration then would not be
required?
No, it doesn't put anything. A cite of the standard above says that no prototype is necessary. Each file is compiled independently so when src1.c is compiled, it doesn't know anything about src2.c and a go function definition inside.
So everyone that says, it is posible to pass whatever number and types
of arguments in absence of prototype is wrong. They are promoted to
intS in this case, but have to agree with those specified in
definition.
It is possible and I've faced a couple of obscure bugs after system-wide change that compiled just fine without any warnings for some reason (actually, it's undefined behavior).
Again, since each *.c file is compiled independently, there is now way it can check the number of arguments and their types of go function defined in another translation unit. If the function takes more arguments that you have provided, the "unused" arguments will be filled with some random data. You should keep in mind, that if arguments don't match - it's undefined behavior, which means that anything can happen.
First of all, function declarations should be put into header files. Now an answer to your question:
When you compile both the files, then at the time of linking, the linker finds the symbol definition of go() in src2.o and thus resolves the symbol reference in the executable, this is the reason why your program works.
You are trying to use sizeof() which is a compile time operator and it would thus output 1 since you are suing it on a character.
You are also passing an integer value >255 to a char variable, this would cause overflow and t would store 1789modulo255 .
When you make one executable using these two source files then final executable will have
definition of go() so no need of it.
But it's better to put declaration in a header file and then include that header file in both the source files
here is header file someheader.h
#ifndef __SMH_
#define __SMH_
int go(char);
#endif
now include it like this
#include "someheader.h"
in src1.c and src2.c
By default
go() will be int go(). i.e returns int and accepts any number of arguments. so your actual function matches with default function type.
When the compiler sees go() in str1.c it assumes that the function is defined elsewhere. It's only at the link time that the linker searches for
the definition of go().
I think, you are compiling the two files separately and link them together which is fine. Because at link time the defintion of go() exists.
If you try to compile str1.c (gcc str1.c as opposed to gcc -c str1.c)separately, you'll get the error about go() not being found by linker.
UPDATE:
Even the implicit declaration by the compiler is not standard compliant (since C99).
Technically, every function should have a prototype irrespective of its return type if it is called before compiler can see it's defintion. The implicit declaring an int returning function is no longer valid ( valid in C89) and has been removed since C99 (and C11).
Although, most compilers still issue only a warning about this, not an error. But if some compiler refuses to compile due to function prototype not being present, then you can't complain about it as it is not standard compliant.
Is declaring an header file essential? This code:
main()
{
int i=100;
printf("%d\n",i);
}
seems to work, the output that I get is 100. Even without using stdio.h header file. How is this possible?
You don't have to include the header file. Its purpose is to let the compiler know all the information about stdio, but it's by no means necessary if your compiler is smart (or lazy).
You should include it because it's a good habit to get into - if you don't, then the compiler has no real way to know if you're breaking the rules, such as with:
int main (void) {
puts (7); // should be a string.
return 0;
}
which compiles without issue but rightly dumps core when running. Changing it to:
#include <stdio.h>
int main (void) {
puts (7);
return 0;
}
will result in the compiler warning you with something like:
qq.c:3: warning: passing argument 1 of ‘puts’ makes pointer
from integer without a cast
A decent compiler may warn you about this, such as gcc knowing about what printf is supposed to look like, even without the header:
qq.c:7: warning: incompatible implicit declaration of
built-in function ‘printf’
How is this possible? In short: three pieces of luck.
This is possible because some compilers will make assumptions about undeclared functions. Specifically, parameters are assumed to be int, and the return type also int. Since an int is often the same size as a char* (depending on the architecture), you can get away with passing ints and strings, as the correct size parameter will get pushed onto the stack.
In your example, since printf was not declared, it was assumed to take two int parameters, and you passed a char* and an int which is "compatible" in terms of the invocation. So the compiler shrugged and generated some code that should have been about right. (It really should have warned you about an undeclared function.)
So the first piece of luck was that the compiler's assumption was compatible with the real function.
Then at the linker stage, because printf is part of the C Standard Library, the compiler/linker will automatically include this in the link stage. Since the printf symbol was indeed in the C stdlib, the linker resolved the symbol and all was well. The linking was the second piece of luck, as a function anywhere other than the standard library will need its library linked in also.
Finally, at runtime we see your third piece of luck. The compiler made a blind assumption, the symbol happened to be linked in by default. But - at runtime you could have easily passed data in such a way as to crash your app. Fortunately the parameters matched up, and the right thing ended up occurring. This will certainly not always be the case, and I daresay the above would have probably failed on a 64-bit system.
So - to answer the original question, it really is essential to include header files, because if it works, it is only through blind luck!
As paxidiablo said its not necessary but this is only true for functions and variables but if your header file provides some types or macros (#define) that you use then you must include the header file to use them because they are needed before linking happens i.e during pre-processing or compiling
This is possible because when C compiler sees an undeclared function call (printf() in your case) it assumes that it has
int printf(...)
signature and tries to call it casting all the arguments to int type. Since "int" and "void *" types often have same size it works most of the time. But it is not wise to rely on such behavior.
C supprots three types of function argument forms:
Known fixed arguments: this is when you declare function with arguments: foo(int x, double y).
Unknown fixed arguments: this is when you declare it with empty parentheses: foo() (not be confused with foo(void): it is the first form without arguments), or not declare it at all.
Variable arguments: this is when you declare it with ellipsis: foo(int x, ...).
When you see standard function working then function definition (which is in form 1 or 3) is compatible with form 2 (using same calling convention). Many old std. library functions are so (as desugned to be), because they are there form early versions of C, where was no function declarations and they all was in form 2. Other function may be unintentionally be compatible with form 2, if they have arguments as declared in argument promotion rules for this form. But some may not be so.
But form 2 need programmer to pass arguments of same types everywhere, because compiler not able to check arguments with prototype and have to determine calling convention osing actual passed arguments.
For example, on MC68000 machine first two integer arguments for fixed arg functions (for both forms 1 and 2) will be passed in registers D0 and D1, first two pointers in A0 and A1, all others passed through stack. So, for example function fwrite(const void * ptr, size_t size, size_t count, FILE * stream); will get arguments as: ptr in A0, size in D0, count in D1 and stream in A1 (and return a result in D0). When you included stdio.h it will be so whatever you pass to it.
When you do not include stdio.h another thing happens. As you call fwrite with fwrite(data, sizeof(*data), 5, myfile) compiler looks on argruments and see that function is called as fwrite(*, int, int, *). So what it do? It pass first pointer in A0, first int in D0, second int in D1 and second pointer in A1, so it what we need.
But when you try to call it as fwrite(data, sizeof(*data), 5.0, myfile), with count is of double type, compiler will try to pass count through stack, as it is not integer. But function require is in D1. Shit happens: D1 contain some garbage and not count, so further behaviour is unpredictable. But than you use prototype defined in stdio.h all will be ok: compiler automatically convert this argument to int and pass it as needed. It is not abstract example as double in arument may be just result of computation involving floating point numbers and you may just miss this assuming result is int.
Another example is variable argument function (form 3) like printf(char *fmt, ...). For it calling convention require last named argument (fmt here) to be passed through stack regardess of its type. So, then you call printf("%d", 10) it will put pointer to "%d" and number 10 on stack and call function as need.
But when you do not include stdio.h comiler will not know that printf is vararg function and will suppose that printf("%d", 10) is calling to function with fixed arguments of type pointer and int. So MC68000 will place pointer to A0 and int to D0 instead of stack and result is again unpredictable.
There may be luck that arguments was previously on stack and occasionally read there and you get correct result... this time... but another time is will fail. Another luck is that compiler takes care if not declared function may be vararg (and somehow makes call compatible with both forms). Or all arguments in all forms are just passed through stack on your machine, so fixed, unknown and vararg forms are just called identically.
So: do not do this even you feel lucky and it works. Unknown fixed argument form is there just for compatibility with old code and is strictly discouraged to use.
Also note: C++ will not allow this at all, as it require function to be declared with known arguments.
#include<stdio.h>
int f();
int main()
{
f(1);
f(1,2);
f(1,2,3);
}
f(int i,int j,int k)
{
printf("%d %d %d",i,j,k);
}
it is running fine(without any error) ...can u plz explain how it executes ?
how f(1) and f(1,2) links to f(int,int,int) ?
You must have a different definition of "error" to me :-) What gets printed the first two times you call your f function? I get
1 -1216175936 134513787
1 2 134513787
1 2 3
for my three function calls.
What you're seeing is a holdover from the very early days of C when people played footloose and fancy-free with their function calls.
All that is happening is that you are calling a function f and it's printing out three values from the stack (yes, even when you only give it one or two). What happens when you don't provide enough is that your program will most likely just use what was there anyway, usually leading to data issues when reading and catastrophic failure when writing.
This is perfectly compilable, though very unwise, C. And I mean that in a very real, "undefined behaviour", sense of the word (referring specifically to C99: "If the expression that denotes the called function has a type that does not include a prototype, ... if the number of arguments does not equal the number of parameters, the behaviour is undefined").
You should really provide fully formed function prototypes such as:
void f(int,int,int);
to ensure your compiler picks up this problem, and use ellipses (...) in variable parameter functions.
As an aside, what usually happens under the covers is that the calling function starts with a stack like:
12345678
11111111
and pushes (for example) two values onto a stack, so that it ends up like:
12345678
11111111
2
1
When the called function uses the first three values on the stack (since that's what it wants), it finds that it has 1, 2 and 11111111.
It does what it has to do then returns and the calling function clears those two values off the stack (this is called a caller-makes-good strategy). Woe betide anyone who tries this with a callee-makes-good strategy :-) although that's pretty unusual in C since it makes variable argument functions like printf a little hard to do.
This declaration:
int f();
...tells the compiler "f is a function that takes some fixed number of arguments, and returns int". You then try to call it with one, two and three arguments - C compilers are conceptually one-pass (after preprocessing), so at this point, the compiler doesn't have the information available to argue with you.
Your actual implementation of f() takes three int arguments, so the calls which only provide one and two arguments invoke undefined behaviour - it's an error which means that the compiler isn't required to give you an error message, and anything could happen when you run the program.
int f();
In C this declares a function which take a variable number of arguments i.e. it's equivalent to the following in C++
int f(...);
To check this use the following instead of int f();
int f(void);
This will cause the compiler to complain.
Please note: A C linker quirk is also involved here...the C linker does not validate the arguments being passed to a function at the point of invocation and simply links to the first public symbol with the same name. Thus the use of f() in main is allowed because of the declaration of int f(). But the linker binds the function f(int, int, int) during link time at the invocation sites. Hope that makes some sense (please let me know if it doesn't)
It runs fine since int f() means what other answer has already said: it means unspecified number of arguments. This mean you can call it with the number of arguments that you want (also more than 3), without the compiler saying anything about it.
The reason why it works "under the cover", is that arguments are pushed on the stack, and then accessed "from" the stack in the f function. If you pass 0 arguments, the i, j, k of the function "corresponds" to values on the stack that, from the function PoV, are garbage. Nonetheless you can access their values. If you pass 1 argument, one of the three i j k accesses the value, the others get garbage. And so on.
Notice that the same reasoning works if the arguments are passed in some other way, but anyway these are the convention in use. Another important aspect of these conventions is that the callee is not responsible for adjusting the stack; it is up to the caller, that knows how many argument are pushed for real. If it would be not so, the definition of f could suggest that it has to "adjust" the stack to "release" three integer, and this would cause a crash of some kind.
What you've written is fine for the current standard (on gcc compiles with no warnings even with -std=c99 -pedantic; there's a warning, but it's about the missing int in front of the f definition), even though many people finds it disgusting and call that an "obsolescent feature". For sure, your usage in the example code does not show any usefulness, and likely it can help busting bugs a more binding usage of prototypes! (But still, I prefer C to Ada)
add
A more "useful" usage of the "feature" that does not trigger the "undefined behaviour" issue, could be
#include<stdio.h>
int f();
int main()
{
f(1);
f(2,2);
f(3,2,3);
}
int f(int i,int j,int k)
{
if ( i == 1 ) printf("%d\n", i);
if ( i == 2 ) printf("%d %d\n", i, j);
if ( i == 3 ) printf("%d %d %d\n", i, j, k);
}
When you compile the same program using g++ compiler you see the following errors -
g++ program.c
program.c: In function `int main()':
program.c:2: error: too many arguments to function `int f()'
program.c:6: error: at this point in file
program.c:2: error: too many arguments to function `int f()'
program.c:7: error: at this point in file
program.c:2: error: too many arguments to function `int f()'
program.c:8: error: at this point in file
program.c: At global scope:
program.c:12: error: ISO C++ forbids declaration of `f' with no type
Using gcc with option -std=c99 just gives a warning
Compile the same program with the same standard which g++ is having by default, gives the following message:
gcc program.c -std=c++98
cc1: warning: command line option "-std=c++98" is valid for C++/ObjC++ but not for C
My answer then would be or c compilers conform to a different standard which is not as restrictive as the one which c++ conforms to.
In C a declaration has to declare at least the return type. So
int f();
declares a function that returns the type int. This declaration doesn't include any information about the parameters the function takes. The definition of the function is
f(int i,int j,int k)
{
printf("%d %d %d",i,j,k);
}
Now it is known, that the function takes three ints. If you call the function with arguments that are different from the definition you will not get a compile-time error, but a runtime error (or if you don't like the negative connotation of error: "undefined behavior"). A C-compiler is not forced by the standard to catch those inconsistencies.
To prevent those errors, you should use proper function prototypes such as
f(int,int,int); //in your case
f(void); //if you have no parameters