tags:

views:

299

answers:

6
#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) ?

+14  A: 

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.

paxdiablo
afaik the `f()` is __still__ described in the standard, so it is fine (and in fact it compiles with just 1 warnign with `-std=c99 -pedantic`, and the warning is not about that); virtual -1 for having not explained why it __works__
ShinTakezou
Well, yes, it's still _valid_ but so is `setjmp` and `longjmp` - that doesn't necessarily make them a good _idea_ :-) In any case, I added a bit as to why it most likely works.
paxdiablo
You're quoting the wrong bit of the spec - f() is a function without a prototype not a function with a variable number of arguments, and its definition does not indicate it takes a variable number of arguments - in C99 section 6.5.2.2 "If the expression that denotes the called function has a type that does not include a prototype, [... promotions elided ]. If the number of arguments does not equal the number of parameters, the behavior is undefined."
Pete Kirkham
@paxdiablo I did too. Virtual -1 removed, but still I disagree with the general picture that those things are bad in absolute. If we need more "contraints", C flexibility is not for us. Likely it is because my programming backgrounds come from assembly, and so I do like what C still allows and I see "why" it allows it in term of how the code could be "translated" into asm. On the other hand, it has the useful "tools" to write more "robust" code, ... but I still appreciate the freedom to choose to use not those tools (provided I know what I am doing...!)
ShinTakezou
The fact that it compiles has nothing to do with the correctness of what is being done. `*((int*)NULL) = 37;` is perfectly valid code in c/c++ but is not correct and will cause undefined behavior. Declaring a function `int f()` is in the standard and declares a function that returns an `int` and takes an unknown number of arguments of unknown types, but unknown does not mean that you can call it with whatever you want to. You are telling the compiler to stop bugging you because *you know* what you are doing.
David Rodríguez - dribeas
Fixed, @Pete, thanks for that.
paxdiablo
( @paxdiablo there's a chance you had put the explanation before my comments so that it becomes "blind"? If it is so, excuse me for being caught in the same kind of "hurry trap" for which I usually "blame" others )
ShinTakezou
No probs @ShinTakezou, I was probably doing the edit when your comment came through. That happens occasionally.
paxdiablo
@David Rodriguez - dribeas, the OP is not asking why he can't use the code in production code. He's asking why it works, and the undefined behaviour is there for that, and so it is part of the code, that must work that way, or it wouldn't have shown the point. It is useful to remember it... but this does not mean the "feature" can't be used in a more "defined behavioural" way. Still, the question _why_ it would work, is there. If the user would have written a more interesting working code, we couldn't have focused on the "undefined behaviour" aspect.
ShinTakezou
@ShinTakezou: I guess we are having an issue with the fine print, that is with the precise interpretation of what *works* means. To me, it *looks* as if it worked, but that does not mean that it *works*. In my interpretation of the word *works* means that can be used with the expected result in other situations. To make it utterly stupid, if you intend to initialize a variable to 0, using `random()` may seem to work if in your current test the returned value *is* 0, but it does not work as different runs will fail to produce the 0.
David Rodríguez - dribeas
Undefined behavior *does not work*. Then again, I believe that we agree in the contents, and the whole argument is on the different interpretations of the language we use. I hope that my intent is clear and understandable :)
David Rodríguez - dribeas
@dribeas your intent is clear, but it is not about what the OP is asking.(explained already in another comment). Suppose the code i `#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);}` . Now, you can focus on why it works. The OP example code was just "sloppy", but made his point.
ShinTakezou
@dribeas moreover the `how f(1) and f(1,2) links to f(int,int,int)` part makes me think he has some C++ background and thought about overloading: the f(1) and f(1,2) "signature" does not correspond to exsting function, so he expect an error (at compile time)
ShinTakezou
+2  A: 
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)

SDX2000
No, `int f();` does not declare a function which takes a variable number of arguments. It declares a function which takes a *fixed*, but unspecified, number of arguments.
caf
fixed but unspecified is variable in my dictionary...YMMV
SDX2000
int f(void) is giving warning ...but not error ..without prototype f gets call ..
kedar
That's a tricky one but I'd have to go with @caf on that one. Fixed/unspecified means that the callee must know how many arguments it wants and that's all it can get. It gets none of the fancy varargs stuff that would allow it to handle an arbitrary number of parameters like `printf` and its brethren.
paxdiablo
for the calling convention in use, the callee is totally ignorant about how many arguments really exist on the stack. The vararg stuff is there for syntax (to allow the compiler to do checks), and to produce the right code on compile time; but since at runtime the callee knows no the real num of args, unless you pass in as (first)arguments, you can produce the same kind of "undefined behaviour" even using vararg.
ShinTakezou
... and `printf` expect a certain number of arguments as its format says (cmpilers can do extra checking, but they can also avoid them, and again the same kind of undefined behaviour can appear, e.g. `printf("%d %d\n", 5);`
ShinTakezou
@SDX2000, your dictionary is wrong then, if you're talking about C. `int printf();` is **NOT** a valid declaration for printf because it takes a variable number of arguments. On systems where the ABI calling convention is to pass arguments in registers when possible, it makes a big difference, and your program actually WILL NOT WORK if you declare printf that way.
R..
@kedar you can force the compiler (at least, gcc which is behind dev-cpp) to rise errors instead of warning in that circumstance; otherwise, it warns you that there's something odd, and you are free to decide to ignore it or if it is important to fix it (usually, it is and it is why prototype help makng better code)
ShinTakezou
@paxdiablo "Fixed/unspecified means that the callee must know how many arguments it wants and that's all it can get." Then the C++ version is also fixed/unspecified by your definition. The callee doesn't have a clue about how many parameters (or their type/sizes) are on the stack even when using vararg macros. Printf uses information from the format string to deduce argument information...this has been the source of numerous security bugs in the past (and continues to be so).
SDX2000
@SDX2000, I may not have made myself clear (that's my fault, not yours). What I meant was: with varargs, a function can handle an arbitrary number of args (and yes, it has to be told how many, either with a "format string" or a sentinel). With non-varargs, the function is defined as having N parameters and it can only get those N (putting aside any non-portable stack trickery of course).
paxdiablo
Or another way of putting it - with a function declared `f();`, the compiler is free to use a callee-cleans-up-stack calling convention when calling that function, like `stdcall` on x86. With a genuine varargs function, it is not.
caf
+3  A: 

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.

caf
in the perspective of the question, we can still say that the program __works__ and contains no error (error and undefined behaviour belong to different "error" domain)
ShinTakezou
No "undefined behaviour" program _ever_ works, even if the undefined behaviour is to produce the correct results :-)
paxdiablo
This program __works__ since does what the user wants it to do to show us its question. Its undefined behaviour is put there on purpose (or the question would not exist at all), so it __works__ fine. The question of the user is then why, since likely he expect to be not able to call `f` that ways without a compiler error.
ShinTakezou
@ShinTakezou: 'a broken clock (analog) gives the correct time twice a day', If you only look at the clock when it is providing the correct time, does it make the clock functional? Code that causes undefined behavior can still provide correct results each so often.
David Rodríguez - dribeas
the user has not specified what it is the correct result for him. He's not saying "I would expect 1 2 3 output anyway". We can suppose the correct expected result is a compiler error, or a crash (`it's running fine, without errors` he says). Instead, the program prints something and exit correctly. So, it __works__ and he's asking why he does not receive a compiler error or a crash
ShinTakezou
+1  A: 

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);
}
ShinTakezou
I take severe issue with saying that what the OP has written is "fine". It could quite easily crash - for example, under a "callee-adjusts-stack" calling convention, like "stdcall" (which you allude to in your answer). It would be fine if the `f(1);` and `f(1, 2);` calls were omitted.
caf
alas the pascal-like calling convention are not in use (I won't say never, but in C they are likely almost never used). See other comments where I've put some code so that people can focus answering his question, instead of on the fact that he wrote fastly a code that results in a "undefined behaviour", but shown anyway the point of the real question
ShinTakezou
@caf just to make my words more clear; recently I've written code for a code-golf. I hardly say it is good C;but it is not the point of the code-golf, so it is not worth focusing on it: from the code-golf PoV, it is "good" code.To show the question of the user, the code is fine and works (i.e. no compile time error nor crash);as already written in comments,the `how f(1) and f(1,2) links to f(int,int,int)` part makes me think he thought an error (btw for C++ std, it gives compile time error, and likely this sounds more logical to the OP) should arise
ShinTakezou
Sure, but I feel that it's also important to point out that it's only working (inasmuch as it isn't just crashing) by a fluke of his implementation, and not by design of the language. By the way, the Win32 API uses a callee-adjusts-stack calling convention...
caf
@caf C std function does not, so as far as one knows it, he can use this "feature" being sure it won't crash. on the other hand, to say that a function expects a different calling convention, a special "attribute" must be added and I believe that in that case the compiler will raise a warning/error. Sure it is important to say it, as a side note. But here there's more much about this topic than why it can be, in general, done!
ShinTakezou
C standard functions (other than varargs functions) can certainly use a callee-adjusts-stack convention by default, and it was even common on 16 bit platforms. This is precisely *why* the C standard does not allow varargs functions to be called without a prototype in scope - `f();` is not a good enough declaration if `f` is a varargs function, because the compiler may need to use a different calling convention. Your "add" section *still* invokes undefined behaviour, and assumes that stack arguments are pushed right-to-left, which certainly isn't required and isn't always the case.
caf
@¢af it assumes a specific calling convention (which is the one widely used on x86 machines), which fixes also the order of the arguments on stack. The problem (if any) could be that C does not allow `f(...)`, while it allows `f(T x, ...)` (ISO C requires named arg before ...). C++ allows `f(...)`; to do something similar in C, we can use `f()`. Maybe it is something the standard will "fix" in future, nonetheless currently it compiles with standard compliance, and in the best case it is also usable as feature, if you know what you're doing. Thus the ans for the OP's Q
ShinTakezou
A: 

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.

Devil Jin
A: 

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
Lucas