Default argument and parameter promotions in C
Asked Answered
M

1

6

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.

Mordacious answered 16/5, 2020 at 10:43 Comment(0)
C
7

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

Crofter answered 16/5, 2020 at 12:45 Comment(23)
And then you can also have short add_shorts(a, b) short a, b; { return a+b; }, where the caller will still provide possibly-promoted int values, but then the function definition decides to convert them when called.Bogie
I understood everything you explained but still my doubt remains there. Which parameters they are talking about in last 3 lines of paragraph, as both the function declaration and definition are prototype less. And what is parameter promotion ?? I understood that arguments get promoted but what is this parameter promotion??Mordacious
Note: cppreference is not a source on wisdom or truth.Narration
If a function is prototype-less it means it is a K&R style function. I guess you thought it was a function with no parameters???Crofter
@Crofter No, without a prototype, everything (both return value and argument(s)) is assumed to be int .Narration
@Narration Aside from the standard (which costs money), no other source is trustworthy. I chose cppreference because it is easy to browse through. If you have a better source please link to it. Don't link to a draft standard though, you can tell from the question that the OP already has a draft/the actual standard.Crofter
@Narration That is not true. Look at @aschepler's function. If you replace the short with double or any pointer type, the arguments are NOT converted to int!!! As I said in my answer, only smaller integer types get passed as int.Crofter
There is no prototype (or: a wrong one) in scope, there. Also, the example is incomplete. (and irrelevant). It happens by coincidence, caused by the promotions.Narration
@Narration I think you too don't understand what a function without a prototype means. double f(double a) {return 2.0;} has prototype, but double f(a) double a; {return 2.0;} does not. Any standard function has prototype and any K&R style function does not. I don't know what you mean by a prototype being in scope.Crofter
@Crofter Yes exactly i thought that way. and what about the parameter promotions ??Mordacious
@Noshiii A quick google search pops up "default argument promotions". I guess it's different terminology for the same thing :) I just did a google search so I might be wrong hereCrofter
@Crofter Okay if that's the case how will we explain this : "the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined" :(Mordacious
The parameters are the ones in the prototype (or the K&R-pseudo-prototype). Arguments are the ones actually passed. Updated my answer. Look at the printf() example. I am passing an int but the function expects a double. Integers are not binary compatible with floating-point, so UB occurs because default argument conversion does not convert int to double as we want, it leaves it alone. In the add_shorts example, the argument promotions pass an int which is binary compatible with short (what the function expects), so no UB there.Crofter
ohhhhh, i finally got it what they meant to say. This means the last three lines are meant for K&R style function definition . Right ?( When function def is prototype less).Mordacious
Yes, K&R style functions but also varargs parameters. Basically the standard has a very fancy way of saying that it is UB to pass something which was not expected by the function (like passing an int instead of a double). It is UB because the compiler cannot know what the function expects (such information is not included in K&R functions nor in varargs ellipsis).Crofter
@DarkAtom: Further complicating things is the fact that the Standard made no attempt to consider things like whether commonplace implementations should support the use of fprintf specifier %X to output values of type unsigned char or unsigned short without explicitly casting to unsigned first. While there might conceivably be some platforms where it would be impractical to support such usage, requiring that programs that will never target such platforms use %hhX or %hX format specifiers would waste memory and time while serving no useful purpose.Skaw
@Skaw I never understood the point of those h format specifiers for printf, other than to be consistent with scanf(). If you have a short or unsigned char or whatever small int type, you can pass it to %d and it will work with well-defined behavior (because all of them fit on int).Crofter
@DarkAtom: Some implementations will process %02hhX in a way that outputs at most two hex digits, though I'm not sure if the Standard would require that behavior be regarded as defined in circumstances that would otherwise result in outputting more (e.g. outputting a negative value of a signed character type). I would guess that the hh specifier is included because at least some implementations would support its use to force truncation of values, but mandating that all implementations support it will waste code space on when processing programs that never use it.Skaw
cppreference is keeping up with latest draft standard, where "functions defined like f1()" are their own thing, since K&R functions were removed.Nightdress
@Nightdress cppreference marks latest draft changes as C2x. All features that were removed are marked as such and are kept on the website for legacy purposes (to my knowledge), like the gets function, for example.Crofter
right, old parameter-less K&Rs are described there as noptr-declarator ( identifier-list(optional) ) (2) (until C2x), new parameter-less no-longer-K&R functions are noptr-declarator ( ) (3) (since C2x). Unless you're talking about some other part of the page?Nightdress
@Nightdress Woah! Good catch, totally missed that! But...if they remove support for K&R functions in C2x, why not remove support for the empty () too? Or, even better, make it equivalent to (void)? It is UB to pass any parameters to it anyway, so why not turn the UB into a compiler error?Crofter
they did make them equivalent to (void) as cppreference says "equivalent to function declaration with the parameter-list consisting of a single keyword void". In the current C2x open-std.org/jtc1/sc22/wg14/www/docs/n2479.pdf it's in "6.7.6.3 Function declarators" pp13Nightdress

© 2022 - 2024 — McMap. All rights reserved.