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.

LocalHost
  • 910
  • 3
  • 8
  • 24

1 Answers1

6

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

DarkAtom
  • 2,589
  • 1
  • 11
  • 27
  • 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. – aschepler May 16 '20 at 13:17
  • 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?? – LocalHost May 16 '20 at 13:36
  • Note: cppreference is not a source on wisdom or truth. – wildplasser May 16 '20 at 13:44
  • 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??? – DarkAtom May 16 '20 at 13:46
  • @DarkAtom No, without a prototype, **everything** (both return value and argument(s)) is assumed to be `int` . – wildplasser May 16 '20 at 13:48
  • @wildplasser 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. – DarkAtom May 16 '20 at 13:49
  • @wildplasser 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`. – DarkAtom May 16 '20 at 13:51
  • 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. – wildplasser May 16 '20 at 13:54
  • @wildplasser 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*. – DarkAtom May 16 '20 at 13:56
  • @DarkAtom Yes exactly i thought that way. and what about the parameter promotions ?? – LocalHost May 16 '20 at 13:58
  • @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 here – DarkAtom May 16 '20 at 13:59
  • @DarkAtom 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" :( – LocalHost May 16 '20 at 14:02
  • 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. – DarkAtom May 16 '20 at 14:09
  • 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). – LocalHost May 16 '20 at 15:01
  • 1
    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). – DarkAtom May 16 '20 at 15:08
  • @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. – supercat May 20 '20 at 17:43
  • @supercat 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`). – DarkAtom May 20 '20 at 18:49
  • @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. – supercat May 20 '20 at 19:04
  • cppreference is keeping up with latest draft standard, where "functions defined like f1()" are their own thing, since K&R functions were removed. – Cubbi May 28 '20 at 20:31
  • @Cubbi 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. – DarkAtom May 28 '20 at 20:43
  • 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? – Cubbi May 28 '20 at 20:46
  • @Cubbi 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? – DarkAtom May 28 '20 at 20:49
  • 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 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2479.pdf it's in "6.7.6.3 Function declarators" pp13 – Cubbi May 28 '20 at 20:55