I know that how arguments are passed to functions is not part of the C standard, and is dependent on the hardware architecture and calling convention.
I also know that an optimizing compiler may automatically inline functions to save on call overhead, and omit code that has no "side effects".
But, I have a question about a specific case:
Lets say there is a non trivial function that can not be inlined or removed, and must be called, that is declared to take no arguments:
int veryImportantFunc() {
/* do some important stuff */
return result;
}
But this function is called with arguments:
int result = veryImportantFunc(1, 2, 3);
Is the compiler allowed to call the function without passing these arguments?
Or is there some standard or technical limitation that would prevent this kind of optimization?
Also, what if argument evaluation has side effects:
int counter = 1;
int result = veryImportantFunc(1, ++counter, 3);
Is the compiler obligated to evaluate even without passing the result, or would it be legal to drop the evaluation leaving counter == 1?
And finally, what about extra arguments:
char* anotherFunc(int answer) {
/* Do stuff */
return question;
}
if this function is called like this:
char* question = anotherFunc(42, 1);
Can the 1 be dropped by the compiler based on the function declaration?
EDIT: To clarify: I have no intention of writing the kind of code that is in my examples, and I did not find this in any code I am working on.
This question is to learn about how compilers work and what the relevant standards say, so to all of you who advised me to stay away from this kind of code: thank you, but I already know that.
To begin with, "declared to take no arguments" is wrong. int veryImportantFunc() is a function accepting any arguments. This is obsolete C style and shouldn't be used. For a function taking no arguments, use (void).
Is the compiler allowed to call the function without passing these arguments?
If the actual function definition does not match the number of arguments, the behavior is undefined.
Also, what if argument evaluation has side effects
Doesn't matter, since arguments are evaluated (in unspecified order) before the function is called.
Is the compiler obligated to evaluate even without passing the result, or would it be legal to drop the evaluation leaving counter == 1?
It will evaluate the arguments and then invoke undefined behavior. Anything can happen.
And finally, what about extra arguments:
Your example won't compile, as it isn't valid C.
The following quotes from the C standard are relevant to your different questions:
6.5.2.2 Function calls
...
2. If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters.
...
4. An argument may be an expression of any complete object type. In preparing for the call to a function, the arguments are evaluated, and each parameter is assigned the value of the corresponding argument.
...
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. 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.
...
10. There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call. Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function.
Lets say there is a non trivial function that can not be inlined or removed, and must be called, that is declared to take no arguments:
int veryImportantFunc() {
/* do some important stuff */
return result;
}
But this function is called with arguments:
There are two possibilities:
the function is declared with a "full" prototype such as
int veryImportantFunc(void);
in this case the call with extra arguments won't compile, as the number of parameters and arguments must match;
the function is declared as taking an unspecified number of arguments, i.e. the declaration visibile to the call site is
int veryImportantFunc();
in this case, the call is undefined behavior, as the usage doesn't match the actual function definition.
All the other considerations about optimization aren't particularly interesting, as what you are trying to do is illegal however you look at it.
We can stretch this and imagine a situation where passing extra useless arguments is legal, for example a variadic function never using the extra arguments.
In this case, as always, the compiler is free to perform any such optimization as long as the observable behavior isn't impacted, and proceeds "as if" performed according to the C abstract machine.
Given that the details of arguments passing aren't observables1, the compiler could in line of principle optimize away the argument passing, while the arguments evaluation may still need to be done if it has some observable impact on the program state.
That being said, I have a hard time imagining how such optimization may be implemented in the "classical linking model", but with LTCG it shouldn't be impossible.
The only observable effects according to the C standard are IO and reads/writes on volatile variables.
Following Pascals theory, it is better to be wrong in believing the compiler can make this optimisation than be right in believing it doesn’t. It serves no purpose to wrongly define a function; if you really must, you can always put a stub in front of it:
int RealSlimShady(void) {
return Dufus;
}
int MaybeSlimShady(int Mathew, int Mathers) {
Used(Mathew);
Used(Mathers);
return RealSlimShady();
}
Everyone is happy, and if your compiler is worth its salt, there will be 0 code overhead.
Related
I understand that for an ordinary C functions:
g1(f1(), f2() f3())
that the order of evaluating the arguments is unspecified: f3() might be called before f1() or vice versa.
But does the same hold true for varadic functions? The definition of va_arg() says:
Each invocation of the va_arg macro modifies ap to point to the next variable argument.
Though it doesn't specify what it means by 'next' (left to right or right to left?), common use cases make it seem likely that it means left to right.
Furthermore, can one assume that the required first argument is evaluated before (or after) the variable arguments? Or is that also unspecified?
void g2(int a, ...);
I suppose a strict reading of this says that one cannot assume any particular order of evaluation. But it certainly would make writing functions like printf() much more difficult, if not intractable.
The order of evaluation of function arguments does not (strictly) depend on whether the function in question is variadic or not. In both cases, the order is unspecified.
The description of va_arg is just telling you what order it reads the arguments in. By the time you're inside the variadic function, the arguments were already evaluated and passed to the function.
Each invocation of the va_arg macro modifies ap to point to the next variable argument.
This refers to "left to right", with the arguments passed to ....
Furthermore, can one assume that the required first argument is evaluated before (or after) the variable arguments?
This is also unspecified. There is no exception for variadic arguments when it comes to order of evaluation, AFAIK.
The arguments are all evaluated when you call the function, although the order is not specificed. By the time you use the va_arg macro, you have already evaluated all the arguments.
It's the same way if I call f(int a, int b), both a and b have already been evaluated by the time I'm in the body of f.
I understand that for an ordinary C functions [...] the order of evaluating the arguments is unspecified [...] But does the same hold true for varadic functions? The definition of va_arg() says [...]
C specifies that
There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call
(C17 6.5.2.2/7)
That applies to all functions, including variadic ones. Among other things, it means that the specifications for the va_arg macro are irrelevant to the order of evaluation of the actual arguments to the function in which that macro appears. All actual arguments to the function are evaluated before execution of the function body begins.
The only distinction C draws on the calling side between variadic functions and non-variadic functions is in the argument type conversions that apply. The variable arguments to a variadic function (or to a function without an in-scope prototype) are subject to the default argument promotions, whereas that is not the case for non-variadic arguments to functions with in-scope prototypes.
Furthermore, can one assume that the required first argument is evaluated before (or after) the variable arguments? Or is that also unspecified?
It is also unspecified. Again, the only distinction C makes between the semantics of calling a variadic function and of calling a non-variadic one is to do with rules for argument type promotions. And that's not really so much a distinction as it is covering cases that don't otherwise arise, and even then it is in a manner that is consistent with other C semantics for arguments whose types are not specified via a function prototype.
I suppose a strict reading of this says that one cannot assume any particular order of evaluation. But it certainly would make writing functions like printf() much more difficult, if not intractable.
No "strict" reading is required. C does not specify any rules for the relative order of evaluation of arguments to the same function call. Period. But that causes no particular issue with the implementation of variadic functions, because all the arguments are evaluated before execution of the function body starts. Variadic functions are subject to constraints on the order in which the values of the variable arguments are read within the function, but that has nothing to do with the order in which the argument expressions are evaluated on the caller's side.
In C when a function is declared like void main(); trying to input an argument to it(as the first and the only argument) doesn't cause a compilation error and in order to prevent it, function can be declared like void main(void);. By the way, I think this also applies to Objective C and not to C++. With Objective C I am referring to the functions outside classes. Why is this? Thanks for reaching out. I imagine it's something like that in Fortran variables whose names start with i, j, k, l, m or n are implicitly of integer type(unless you add an implicit none).
Edit: Does Objective C allow this because of greater compatibility with C, or is it a reason similar to the reason for C having this for having this?
Note: I've kept the mistake in the question so that answers and comments wouldn't need to be changed.
Another note: As pointed out by #Steve Summit and #matt (here), Objective-C is a strict superset of C, which means that all C code is also valid Objective-C code and thus has to show this behavior regarding functions.
Because function prototypes were not a part of pre-standard C, functions could be declared only with empty parentheses:
extern double sin();
All existing code used that sort of notation. The standard would have failed had such code been made invalid, or made to mean “zero arguments”.
So, in standard C, a function declaration like that means “takes an undefined list of zero or more arguments”. The standard does specify that all functions with a variable argument list must have a prototype in scope, and the prototype will end with , ...). So, a function declared with an empty argument list is not a variadic function (whereas printf() is variadic).
Because the compiler is not told about the number and types of the arguments, it cannot complain when the function is called, regardless of the arguments in the call.
In early (pre-ANSI) C, a correct match of function arguments between a function's definition and its calls was not checked by the compiler.
I believe this was done for two reasons:
It made the compiler considerably simpler
C was always designed for separate compilation, and checking consistency across translation units (that is, across multiple source files) is a much harder problem.
So, in those early days, making sure that a function's call(s) matched its definition was the responsibility of the programmer, or of a separate program, lint.
The lax checking of function arguments also made varargs functions like printf possible.
At any rate, in the original C, when you wrote
extern int f();
, you were not saying "f is a function accepting no arguments and returning int". You were simply saying "f is a function returning int". You weren't saying anything about the arguments.
Basically, early C's type system didn't even have a way of recording the parameters expected by a function. And that was especially true when separate compilation came into play, because the linker resolved external symbols based pretty much on their names only.
C++ changed this, of course, by introducing function prototypes. In C++, when you say extern int f();, you are declaring a function that explicitly takes 0 arguments. (Also a scheme of "name mangling" was devised, which among other things let the linker do some consistency checking at link time.)
Now, this was all somewhat of a deficiency in old C, and the biggest change that ANSI C introduced was to adopt C++'s function prototype notation into C. It was slightly different, though: to maintain compatibility, in C saying extern int f(); had to be interpreted as meaning "function returning int and taking unspecified arguments". If you wanted to explicitly say that a function took no arguments, you had to (and still have to) say extern int f(void);.
There was also a new ... notation to explicitly mark a function as taking variable arguments, like printf, and the process of getting rid of "implicit int" in declarations was begun.
All in all it was a significant improvement, although there are still a few holes. In particular, there's still some responsibility placed on the programmer, namely to ensure that accurate function prototypes are always in scope, so that the compiler can check them. See also this question.
Two additional notes: You asked about Objective C, but I don't know anything about that language, so I can't address that point. And you said that for a function without a prototype, "trying to input an argument to it (as the first and the only argument) doesn't cause a compilation error", but in fact, you can pass any number or arguments to such a function, without error.
With regards to the ANSI C function declaration, how is this an improvement from the old K&R style? I know the differences between them, I just want to know what problems could arise from using the old style and how the new style is an improvement.
Old-style function declarations, in particular, don't allow for compile-time checking of calls.
For example:
int func(x, y)
char *x;
double y;
{
/* ... */
}
...
func(10, 20);
When the compiler sees the call, it doesn't know the types of the parameters of the function func, so it can't diagnose the error.
By contrast:
int better_func(char *x, double y) {
/* ... */
}
...
better_func(10, 20);
will result in a compiler error message (or at least a warning).
Another improvement: prototypes make it possible to have functions with parameters of type float, and of integer types narrower than int (the 3 char types and the two short types). Without a prototype, float is promoted to double, and narrow integer types are promoted to int or to unsigned int. With a prototype, a float argument is passed as a float (unless the function is variadic, like printf, in which case the old rules apply to the variadic arguments).
The C Rationale document discusses this in section 6.7.5.3, probably better than I have:
The function prototype mechanism is one of the most useful additions
to the C language. The feature, of course, has precedent in many of
the Algol-derived languages of the past 25 years. The particular form
adopted in the Standard is based in large part upon C++.
Function prototypes provide a powerful translation-time error
detection capability. In traditional C practice without prototypes, it
is extremely difficult for the translator to detect errors (wrong
number or type of arguments) in calls to functions declared in another
source file. Detection of such errors has occurred either at runtime
or through the use of auxiliary software tools.
In function calls not in the scope of a function prototype, integer
arguments have the integer promotions applied and float
arguments are widened to double. It is not possible in such a call
to pass an unconverted char or float argument. Function
prototypes give the programmer explicit control over the function
argument type conversions, so that the often inappropriate and
sometimes inefficient default widening rules for arguments can be
suppressed by the implementation.
There's more; go read it.
A non-defining function declaration in K&R looks as follows
int foo();
and introduces a function that accepts unspecified number of arguments. The problem with such declaration style is obvious: it specifies neither the number of parameters nor their types. There's no way for the compiler to check the correctness of the call with respect to the number of arguments or their types at the point of the call. There's no way for the compiler to perform the argument type conversion or issue and error message in situations when argument type does not match the expected parameter type.
A function declaration, which is used as a part of function definition in K&R looks as follows
int foo(a, b)
int a;
char b;
{ ...
It specifies the number of parameters, but still does not specify their types. Moreover, even though the number of parameters appears to be exposed by this declaration, it still formally declares foo the same way as int foo(); does, meaning that calling it as foo(1, 2, 3, 4, 5) still does not constitute a constraint violation.
The new style, i.e. declaration with prototype is better for obvious reasons: it exposes both the number and the types of parameters. It forces the compiler to check the validity of the call (with regard to the number and the types of parameters). And it allows the compiler to perform implicit type conversions from argument types to parameter types.
There are other, less obvious benefits provided by prototype declarations. Since the number and types of function parameters are known precisely to both the caller and the function itself, it is possible to choose the most efficient method of passing the arguments (the calling convention) at the point of the call without seeing the function definition. Without that information K&R implementations were forced to follow a single pre-determined "one size fits all" calling convention for all functions.
I'm dealing with some pre-ANSI C syntax. See I have the following function call in one conditional
BPNN *net;
// Some more code
double val;
// Some more code, and then,
if (evaluate_performance(net, &val, 0)) {
But then the function evaluate_performance was defined as follows (below the function which has the above-mentioned conditional):
evaluate_performance(net, err)
BPNN *net;
double *err;
{
How come evaluate_performance was defined with two parameters but called with three arguments? What does the '0' mean?
And, by the way, I'm pretty sure that it isn't calling some other evaluate_performance defined elsewhere; I've greped through all the files involved and I'm pretty sure the we are supposed to be talking about the same evaluate_performance here.
Thanks!
If you call a function that doesn't have a declared prototype (as is the case here), then the compiler assumes that it takes an arbitrary number and types of arguments and returns an int. Furthermore, char and short arguments are promoted to ints, and floats are promoted to doubles (these are called the default argument promotions).
This is considered bad practice in new C code, for obvious reasons -- if the function doesn't return int, badness could ensure, you prevent the compiler from checking that you're passing the correct number and types of parameters, and arguments might get promoted incorrectly.
C99, the latest edition of the C standard, removes this feature from the language, but in practice many compilers still allow them even when operating in C99 mode, for legacy compatibility.
As for the extra parameters, they are technically undefined behavior according to the C89 standard. But in practice, they will typically just be ignored by the runtime.
The code is incorrect, but in a way that a compiler is not required to diagnose. (A C99 compiler would complain about it.)
Old-style function definitions don't specify the number of arguments a function expects. A call to a function without a visible prototype is assumed to return int and to have the number and type(s) of arguments implied by the calls (with narrow integer types being promoted to int or unsigned int, and float being promoted to double). (C99 removed this; your code is invalid under the C99 standard.)
This applies even if the definition precedes the call (an old-style definition doesn't provide a prototype).
If such a function is called incorrectly, the behavior is undefined. In other words, it's entirely the programmer's responsibility to get the arguments right; the compiler won't diagnose errors.
This obviously isn't an ideal situation; it can lead to lots of undetected errors.
Which is exactly why ANSI added prototypes to the language.
Why are you still dealing with old-style function definitions? Can you update the code to use prototypes?
Even standard C compilers are somewhat permissive when it comes to this. Try running the following:
int foo()
{
printf("here");
}
int main()
{
foo(3,4);
return 0;
}
It will, to some's surprise, output "here". The extra arguments are just ignored. Of course, it depends on the compiler.
Overloading doesn't exist in C so having 2 declarations would not work in the same text.
That must be a quite old compiler to not err on this one or it did not find the declaration of the function yet!
Some compilers would not warn/err when calling an undefined function. That's probably what you're running into. I would suggest you look at the command line flags of the compiler to see whether there is a flag you can use to get these warnings because you may actually find quite a few similar mistakes (too many parameters is likely to work just fine, but too few will make use of "undefined" values...)
Note that it is possible to do such (add extra parameters) when using the ellipsis as in printf():
printf(const char *format, ...);
I would imagine that the function had 3 parameters at some point and the last was removed because it was unused and some parts of the code was not corrected as it ought to be. I would remove that 3rd parameter, just in case the stack goes in the wrong order and thus fails to send the correct parameters to the function.
GCC typically yields this warning when the proper header file is not included. This link --> www.network-theory.co.uk/docs/gccintro/gccintro_19.html says that because the function declaration is implicit (rather than explicitly declared via a header) the wrong argument types could actually be passed to the function, yielding incorrect results. I don't understand this. Does this mean the compiler generates code that pushes something, of the machine's word size, onto the stack for the callee to consume, and hopes for the best?
Detail is appreciated.
If the compiler doesn't have specific information about how the argument should be passed, such as when there's no prototype or for arguments that are passed where the prototype have an ellipsis ('...'), the compiler follows certain rules for passing the arguments. These rule basically follow what occurred in pre-standard (or K&R) C - before prototypes were used. Paraphrased from C99 6.5.2.2/6 "Function calls":
* the integer promotions are applied
* if the argument has float type it's promoted to double
After these default argument promotions are applied, the argument is simply copied to wherever the compiler normally copies arguments (generally, the stack). So a struct argument would be copied to the stack.
If the actual function implementation doesn't match how the compiler creates the parameters, then you get undefined behavior (with exceptions for signed/unsigned mismatch if the value can be represented or pointers to char and pointers to void can be mixed/matched).
Also in C90, if the function is implicitly declared (which C99 doesn't permit, though it does permit functions without prototypes), the return value is defaulted as int. Once again, the the actual function returns something else, undefined behavior results.
In classic K&R C, that's pretty much what happened; there were default coercions (anything smaller than (int) was promoted to (int), for example), and for backwards compatibility any function without a prototype is still called that way, but by and large the only indication you got for passing the wrong type was a weird result or maybe a core dump. Which is where you get in trouble, as when the function has a prototype the exact (not coerced/promoted) value is pushed. So if you're passing a (char), if there's a prototype in scope then a single byte is pushed by the caller, otherwise 4 bytes (on most current platforms). If the caller and callee disagree about this, Bad Things happen.