Default argument and parameter promotions in C - c

I was studying about default argument promotions and got stuck at one point. In C 2011 (ISO/IEC 9899:2011), the relevant part seem to be:
§6.5.2.2 Function calls
¶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, 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.
In the last three lines of paragraph it talks about the function type that does not include a prototype while defining it.
It says if the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined.
Now i have a very silly doubt that if both the function declaration and function definition does not include a prototype as mentioned in this paragraph, so about which parameters they are talking about in last three lines of paragraph. And what is the meaning of "parameters after promotion" here as i have only studied about argument promotions. What is "parameter promotions"?
Also can you give example of the exceptional cases mentioned in the last. If someone can explain this with a proper example that would be really appreciable.

Before C was standardized (aka before C89), functions were defined differently. The style is still supported in C11 for backwards-compatibility. Don't use it unless the whole purpose is to have fun:
int add_ints(); //forward-declaration has no parameters
add_ints(a, b)
//implicit type for return and parameters is int, this only works in pre-standard C or C89/C90
//int a, b; //remove this comment in C99/C11 for it to compile (also add return type int)
{
return a + b; //side note: old K&R compilers required parantheses around the return expression
}
In a way, these functions have parameters that behave like varargs. The caller doesn't know what parameters the function expects (same as with varargs). It is able to pass it any parameters and any number of them. However, it is of course undefined behavior if the number of parameters in the call statement doesn't match the number of parameters in the declaration.
Of course, there is a problem that arises from this. If the caller wants to pass a short, how will it know whether the function is expecting a short (and pass it directly) or an int (and needs to convert it)? It cannot, so a common ground was reached. It has been decided that:
char and short get promoted to int
float gets promoted to double
This happens for all functions defined this way (K&R style) and for varargs parameters. This way, a K&R function will never expect a short parameter, thus the compiler will always promote short parameters to int.
Of course, as #aschepler said, you can still define the function like:
short add_shorts(a, b)
short a, b;
{
return a + b;
}
This means that the parameters are first converted to int and passed to the function and only then does the function convert them to short and add them.
Be careful with functions like printf():
printf("%.f", 3); //passes an int: UB and also wrong answer (my compiler prints 0)
printf("%.f", 3.0); //correct
printf("%.f", (double)3); //correct
You may actually see K&R functions quite often, especially if the author didn't pay attention to add the void keyword to a function that takes no parameters:
int f1() //K&R function
{
return 0;
}
int f2(void) //Standard function
{
return 0;
}
int main(void) //Don't forget void here as well :P
{
int a = f1(); //Returns 0
int b = f2(); //Returns 0
int c = f1(100); //UB - invalid number of parameters, in practice just returns 0 :)
int d = f2(100); //Compiler error - parameter number/types don't match
//A good compiler would give a warning for call #3, but mine doesn't :(
}
EDIT: Not sure why, but cppreference classifies functions defined like f1() as their own type of function (parameter-less without void), instead of K&R functions. I don't have the standard in front of me, but even if the standard says the same thing, they should behave the same and they have the history I mentioned.
Default argument promotions
Function declarations in C

Related

What happens to the unspecified arguments in function()? [duplicate]

This question already has answers here:
What does an empty parameter list mean? [duplicate]
(5 answers)
Accessing the parameters passed to a function with an empty parameter list in C
(2 answers)
Closed 2 years ago.
I have been reading the difference between function() and function(void) in C, and I came to know that
an empty parameter list in a function declaration indicates that the
function takes an unspecified number of parameters
So I ran this code:
#include <stdio.h>
void fun();
int main(void)
{
fun(12, 13.22, 1234567890987654321, "wow", 'c');
}
void fun()
{
printf("What happened to those arguments?");
}
I don't understand why C allows this. All the posts I've read so far related to this say things like it's bad practice to use it, it is obsolescent, etc. Also from the above code, I think the type of those arguments is also unspecified. So I just want to know the reason behind "unspecified arguments of unspecified type":
What can be done with those arguments?
Is it possible to access those arguments within the function?
The reason for supporting the notation is historical. Before the first C standard (C89/C90), you couldn't use prototypes in C; prototypes were one of the biggest and most important features of Standard C. All function declarations, therefore, were written the 'empty parentheses' style (when they were written at all; most functions that returned int were not declared at all). The type void was also added in C89/C90, though some compilers supported it before the standard was finalized.
Because it was crucial to the success of C89/C90 that existing code should mostly continue to work, the empty parentheses style had to be allowed by the standard. So, your code might have been written in pre-standard C as:
#include <stdio.h>
int fun(); /* This declaration would probably have been absent */
int main(void)
{
fun(12, 13.22, 1234567, "wow", 'c');
return 0; /* This was required until C99 to give reliable exit status */
}
fun(i, d, l, s, c) /* No return type - implicitly returns int */
long l; /* Defined out of sequence - bad style, but legal */
char c; /* Passed as int; converted to char in function */
char *s; /* Should define all pointer arguments */
double d; /* No definition of i; it was an int by default */
{
printf("This is what happened to those arguments:\n");
printf("i = %d\n", i);
printf("d = %f\n", d);
printf("l = %ld\n", l);
printf("s = [%s]\n", s);
printf("c = %c\n", c);
/* No return statement - don't use the value from the function */
}
For the curious: you could omit the char *s; line in the function definition, and it still compiled and produced the same output. It was a bad idea to try that, though. You could replace the line int fun(); with static fun(); and the code compiles cleanly when no diagnostics are requested.
You get no warnings even now if you compile this file (old31.c) with GCC 9.3.0 using:
$ gcc -std=c90 -o old31 old31.c
$
Your example as written is skirting around the backwards compatibility provisions. Using void means it was new code (it would not have been valid in many pre-standard C compilers because it used void). And new code should not exploit the backwards-compatibility provisions without a good reason. That was true in 1991 as well as in the current millennium (but in 1991, there were a lot more good reasons to exploit the backwards-compatibility provisions). Good pre-standard code usually listed all parameters in the order they were used. Omitted definitions and out of sequence definitions were not entirely satisfactory.
You asked:
What can be done with those arguments?
In the code in the question, nothing can be done with the arguments. The caller pushes the values onto the stack, and pops them off when the function returns. The called function is unaware of their existence and can do nothing with them.
Is it possible to access those arguments within the function?
No — not using any standard mechanism.
There is a difference between a function declaration and a function definition when there is an empty parameter list.
Section 6.7.6.3p14 of the C standard states:
An identifier list declares only the identifiers of the parameters of
the function. An empty list in a function declarator that is
part of a definition of that function specifies that the
function has no parameters. The empty list in a function
declarator that is not part of a definition of that function
specifies that no information about the number or types of the
parameters is supplied.
What this means is that this declaration:
void fun();
Means fun takes an unknown number of parameters. While this definition:
void fun()
{
printf("What happened to those arguments?");
}
Means that fun takes no parameters. So this function call:
fun(12, 13.22, 1234567890987654321, "wow", 'c');
Is invalid and invoked undefined behavior because the number of parameters in the call don't match the actual number of parameters. This is spelled out in section 6.5.2.2p6 regarding the function call operator ():
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.
As for why this is allowed, it is legacy behavior that goes back to pre-standardized versions of C where the type of variables and the return type of functions defaulted to int and the method of declaring functions differed from what they are now.

Default argument promotions according to C standards

I was reading C standard for default argument promotions and got confused over many points. This question shows all the paragraphs that i have doubt on in a proper way.
First of all in Paragraph 6 point 3, it says if the prototype ends with ellipsis the behavior is undefined. Now my doubt is that if we talk about printf, it's prototype also ends with ellipsis but it's behavior is not undefined and in fact it follows the point 1 of paragraph 6. What the standard is trying to explain here? and further it says that if types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined.. Now here my doubt is that if parameters are already declared in the function prototype why in first place arguments are getting promoted.
Than in paragraph 6 point 4,it says that the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined. As here, it is mentioned that the function do not have a prototype, So exactly about what parameters they are talking about ? and how parameters get promoted. I have only studied about argument promotions.
Than in paragraph 7 point 1 what does this line mean : taking the type of each parameter to be the unqualified version of its declared type.
I am having really a very hard time understanding all this. It would be really helpful if you can explain all points with proper examples one by one. I am non native English speaker, if i am misunderstanding some standard's points, please point that mistakes too.
In C 1999 clause 6.5.2.2 paragraph 6, the item labeled 3 in that question is intended to be interpreted with the item labeled 1: If the calling expression use a type that does not have a prototype, and the called function is defined with a prototype that ends with an ellipsis or the promoted arguments types are incompatible with the parameter types, then the behavior is undefined.
So this is not saying you cannot use ellipsis, just that there can be a conflict between the function type used in an expression that calls a function and the function type used in defining the function.
Example:
File Caller.c contains:
void foo(); // No prototype (parameter types are not declared).
int main(void)
{
foo(3, 4);
}
File Function.c contains:
void foo(int x,...) // Prototype (parameter types are declared) and has ellipsis.
{
}
The behavior is undefined because foo is called as if it were void foo(), but it is defined with void foo(int x,...). This mismatch should not occur in modern practice, because declaring functions without prototypes is old-style. It is still supported in C so that old source code can still be compiled, but new source code should not use it. As long as parameter types are always declared, in both function declarations and function definitions, then this situation will never occur.
In paragraph 7, “taking the type of each parameter to be the unqualified versio of its declared type” means to ignore the qualifiers (const, volatile, restrict, or _Atomic). This means it is okay to pass an int argument for a const int parameter, and so on.

How is this definition of main in C acceptable to the compiler [duplicate]

For some odd reason I was copying an example in another language of which does not use types, and forgot to add one in to a function definition parameter, and it worked.
#include <stdio.h>
char toChar(n) {
//sizeof n is 4 on my 32 bit system
const char *alpha = "0123456789ABCDEF";
return alpha[n];
}
int main() {
putchar(toChar(15)); //i.e.
return 0;
}
I am sure that main defaults to int by most compilers of some standard (but only return), is this also a behaviour true for other functions as well or is this implementation defined? It seems just out of the ordinary, my compiler is just a slightly outdated GCC port (MinGW).
K&R-style function declaration:
void foo(n)
int n;
{
}
If type isn't specified, it defaults to int. This is valid in C89, not C99
#Erik's answer is correct, but (IMO) could be misunderstood rather easily.
What you have is a K&R style definition, that's entirely true. Since int is considered the default type, if you have something like: foo(n) { }, it means the return type and the type of n are both int by default.
C99 (mostly) removes the "default int" rule, but does not entirely remove K&R style definitions. That means the definition above is no longer allowed; to define foo with the same types as above, you could still use:
int foo(n)
int n;
{
}
I.e., the K&R style definition is still allowed, but you do have to specify the return type and the parameter type, even if the type is int.
This style of function definition is, however, obsolescent (per §6.11.7). It's really only still allowed for the sake of ancient (pre-standard) code. You don't want to write new code this way, even though it's technically still allowed. For new code, you clearly want to use a prototype-style definition:
int foo(int n) {}
For those who care about such things, the K&R style definition is still used in some new code though. Its terser style can be useful (using the term loosely) in code-golf.
As long as we're at it: #stijn's answer is sort of correct as well. In particular when a function is defined this way (I.e., K&R style), all arguments are subject to default promotions. That means if you pass an integer type smaller than int, it'll be promoted to int before being passed, and if you pass a float, it'll be promoted to double.
afaik this is called argument promotion. i do not recall the exact rules, but it comes down to the compiler being allowed to use int for arguments when he (she?) doesn't know the function prototype yet.

Why does an empty declaration work for definitions with int arguments but not for float arguments?

I thought the difference is that declaration doesn't have parameter types...
Why does this work:
int fuc();
int fuc(int i) {
printf("%d", i);
return 0;
}
but this fails compiling:
int fuc();
int fuc(float f) {
printf("%f", f);
return 0;
}
with the message:
error: conflicting types for ‘fuc’. note: an argument type that has a default promotion can’t match an empty parameter name list declaration
A declaration:
int f();
...tells the compiler that some identifier (f, in this case) names a function, and tells it the return type of the function -- but does not specify the number or type(s) of parameter(s) that function is intended to receive.
A prototype:
int f(int, char);
...is otherwise similar, but also specifies the number/type of parameter(s) the function is intended to receive. If it takes no parameter, you use something like int f(void) to specify that (since leaving the parentheses empty is a declaration). A new-style function definition:
int f(int a, char b) {
// do stuff here...
}
...also acts as a prototype.
Without a prototype in scope, the compiler applies default promotions to arguments before calling the function. This means that any char or short is promoted to int, and any float is promoted to double. Therefore, if you declare (rather than prototype) a function, you do not want to specify any char, short or float parameter -- calling such a thing would/will give undefined behavior. With default flags, the compiler may well reject the code, since there's basically no way to use it correctly. You might be able to find some set of compiler flags that would get it to accept the code but it would be pretty pointless, since you can't use it anyway...
prototype = forward declaration, so you can use it before you tell the compiler what it does. It still has parameters, however.
Useful in a lot of respects!
The declaration int fuc(float); tells the compiler that there exists a function fuc which takes a float and returns an int.
The definition int fuc(float f) { /*...*/ } tells the compiler what fuc actually is and also provides the declaration as well.
The difference between a declaration and definition is the difference between saying that a size 6 blue hat exists and and handing someone a size 6 blue hat: the declaration says that there is such a thing, the definition says that this thing right here is the thing in question.

C prototype functions

As a beginner to C, I can understand the need for function prototypes in the file, but am unsure of a couple things.
First, does every function call outside of the main require a prototype declaration? Are there any conditions where that can change?
Second, do you need a separate function prototype for method overloads?
Function calls in C don't require a prototype to be visible but it is highly recommended that a correct prototype is in scope.
The reason for this is that if the function definition doesn't match the types of the function arguments after the default function argument promotions are performed you are highly likely to get undefined behavior.
Having the correct prototype visible means that the compiler can check the arguments of a function call and warn the programmer if there is a mismatch.
C doesn't allow functions to be overloaded so you can only have a single prototype for any function name.
Default argument promotions can cause unexpected mismatches.
E.g.
int main(int argc, char **argv)
{
short s = 5;
float f = 2.3f;
x(s, f); // x implicitly declared; default argument promotions performed
return 0;
}
int x(short t, float g) // Error: called with an int and a double
{
return (int)(t + g);
}
In the function call, because x has no visible prototype (yet), s will be promoted to int and f will be promoted to double. These are default argument promotions. This then causes a mismatch when the function is defined with a prototype that takes a short and a float even though these are the original types of the arguments that were passed in.
Functions that take a variable number of arguments (i.e. use , ... syntax) must always have a visible prototype at the point at which they are called.
Others have already pointed out that C doesn't require prototypes for functions. I'll just add a couple minor points.
First of all, without a prototype, the arguments to a function always undergo "default promotions" before being passed as parameters, so (for example) all the smaller integer types get promoted to int, and float gets promoted to double. Therefore, without a prototype, it's impossible for a function to receive (for example) a char, short, or float. If the function is defined to receive one of those types, there will be a mismatch between what's passed and what the function expects, giving undefined behavior (any other mismatch gives undefined behavior as well).
Second, if a function is defined with a variable argument list, then you do need a prototype for it, or else calling it will result in undefined behavior.
No, functions are not required to have prototypes. However, they are useful as they allow functions to be called before they are declared. For example:
int main(int argc, char **argv)
{
function_x(); // undefined function error
return 0;
}
void function_x()
{
}
If you add a prototype above main (this is usually done in a header file) it would allow you to call function_x, even though it's defined after main. This is why when you are using an external library that needs to be linked, you include the header file with all the function prototypes in it.
C does not have function overloading, so this is irrelevant.
No. In C not every function requires a prototype. BUT it is good practice to include it. Prototypes help the compiler catch errors.
Of course some C compilers, by default complain if prototypes are missing before the function call, but this behavior is not necessarily a standard.
The above logic applies to overloads too.
Of-course, since you are new to C. I think you better go and look up the difference between a Prototype and Definition of a function to make full sense.

Resources