What does this statement means in C11 standard (about variadic functions)? - c

At $6.5.2.2.6 the C11 standard:
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: ...
What does that means - I really can't understand it (especially the first part). From what I can however it means that defining a function like this:
void func(int a, int b, ...)
{
}
And then calling it is undefined behavior which I think is silly.

The situation is as follows: You can declare a function without a parameter list and call this function:
int main(void)
{
extern void f(); // no parameter list!
char c = 'x';
f(c, 1UL, 3.5f);
}
In this situation, the arguments are default-promoted: The first argument is promoted to either int or unsigned int (depending on the platform), the second remains unsigned long, and the third is promoted to double.
When the program is linked, some translation unit needs to contain the definition of the function. The definition always contains a parameter list, even if it's empty (but an empty parameter list in the definition means that the function takes no parameters, unlike in the declaration-that-is-not-a-definition above, where it just means that no information is provided about the parameters):
void f(int, unsigned long, double)
{
// ...
}
The standardese you quoted now says that the behaviour is undefined if the parameter types in this definition are not compatible with the promoted types of the call, or if the parameter list ends with an ellipsis.
As a corollary, it follows that if you want to use a function with variable arguments (using the facilities of <stdarg.h> to access the arguments), you must declare the function with a prototype:
extern void f(int, ...); // prototype (containing ellipsis)
f(c, 1UL, 3.5f);
Now c is converted to int because the first parameter is typed, and the second and third arguments are default-promoted just as before because they are passed as part of the ellipsis. The definition of f must now use the same declaration. If you will, passing arguments in a way that the <stdarg.h> facilities can access may require advance knowledge from the compiler, so you have to provide the parameter list before making the call.

The wording is a bit confusing. The whole paragraph is talking about the case where no prototype has been declared for the function at the time it is called, so the section you highlighted is for the case where no prototype is declared when the function is called, but a prototype is used when the function is defined. Here is an example:
int main(int argc,char** argv)
{
f(3.0f); /* undefined behavior */
g(3.0); /* undefined behavior */
}
int f(float v)
{
return v;
}
int g(double v,...)
{
return v;
}
In this example, when f is called, no prototype has been declared, so 3.0f is promoted to a double. However, the function is later defined with a prototype which takes a float instead of a double, so the behavior is undefined.
Likewise for g, the behavior is undefined because the elipses are used in the prototype of the definition.

Related

Does C compiler automatically cast literals to parameter types when passing to functions?

I have a doubt regarding the casting of types in C. Is it safe to pass literals to function parameters(expecting different types but compatible types) without explicitly casting? For example here is an example code:
#include<stdio.h>
int sum(int a, int b){
return a + b ;
}
int main(void){
int x = sum(6U,5U) ; /* Does the compiler automatically casts unsigned to int?
or do I need to explicitly cast it sum( (int) 6U, (int) 5U) ?*/
printf("%d \n", x);
return 0;
}
The code runs fine but I wanted to know if this legal or can lead to undefined behaviours (assuming no overflows) ?
If an argument in a function call corresponds to a parameter with a declared type, the argument is converted to the parameter type, per C 2018 6.5.2.2 7.
If the argument does not correspond to a parameter with a declared type, the default argument promotions are performed. This occurs when the argument corresponds to the ... part of a function prototype, per C 2018 6.5.2.2 7, or when called function does not have a prototype, per 6.5.2.2 6.
The default argument promotions consist of the integer promotions (which largely promote integer types narrower than [technically of lower rank] int to int) and promote float to double.
Note that the applicable function type for these conversions is the type of the expression used to call the function. A function may be defined in one place with a prototype, such as int foo(char *a) { ... }, but declared in another place without a prototype, such as int foo();. The declaration visible at the function call is what determines which conversions will be performed. This is particularly so when a function is called via a pointer.
Note that these conversions are not casts. A cast is a specific operator in source code, a type name in parentheses, for which a conversion is performed. The cast, such as (int) in source code, is different from the operation, the same way a + in source code is different from the addition performed for it.

Function calls n1256 6.5.2.2 p5, p6

I'm reading N1256 6.5.2.2 function call. Some parts were hard to understand to me. I googled with bold sentences or some keywords but couldn't find a straightforward answer to my question. So I ask mine.
At 6.5.2.2 p5(emphasize mine)
If the expression that denotes the called function has a type pointer to function returning an
object type, the function call expression has the same type as that object type and has the
value determined as specified in 6.8.6.4. Otherwise, the function call has type void. If
an attempt is made to modify the result of a function call or to access it after the next
sequence point, the behavior is undefined.
Q1. What's the meaning of the bold sentence? Is there a way to modify the result of a function call or access it after the next sequence point?
A part of 6.5.2.2 p6(emphasize mine)
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: ...
At 6.7 p4, for the definition of compatible types.
All declarations in the same scope that refer to the same object or function shall specify
compatible types.
Formal parameters are not an object, just a placeholder. Q2. Can arguments of a function call and a parameter be the same object? Q3. And why does the standard compare a promoted argument with a parameter? If there is a parameter to compare, promotion won't happen because default argument promotion happens only if the compiler doesn't know what data type callee expects.
(Replacing previous answer)
For Q3, this seems to be about the case where the expression denoting the called function does not include a prototype, but the function definition does. In that case the arguments will be subject to promotion.
void foo1(short s) { // defined with a prototype
// ...
}
void foo2(int i) { // defined with a prototype
// ...
}
void bar(void) {
void (*ptr)(); // no prototype
char c = 'x';
short s = 8;
int i = 1234;
long l = 12345678;
ptr = foo1;
ptr(c); // UB; char promoted to int which is not compatible with short
ptr(s); // Also UB! short promoted to int which is not compatible with short
ptr(i); // likewise UB
// AFAICT there is no way to call foo1 through ptr without UB
ptr = foo2;
ptr(c); // OK; char promoted to int which is compatible with int
ptr(s); // likewise OK
ptr(i); // likewise OK
ptr(l); // UB; long is not compatible with int.
}

I want default argument promotion's example

At C standard 6.5.2.2. paragraph 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.
"the expression that denotes the called function has a type that does not include a prototype" What can this be described as code?
char a = 1;
printf("%d", a); /*"the expression that denotes the called function" is printf, and `a`
doesn't have prototype(char). but a can't be replaced as `char a` originally. ...? */
This is my thought that can be applicable to above standard. Is this right?
For historical reasons, functions in C can be declared the old way, without a prototype, as:
type function()
or the new way, with a prototype, as:
type function(type parameter)
type function(type parameter, type parameter)
type function(type parameter, type parameter, type parameter)
… and so on
Thus, a function declaration in which the types of the parameters are given is said to have a prototype. This affects preparation of the arguments when calling a function. With a prototype, the arguments are converted to the declared types of the parameters. Without a prototype, the arguments are converted to default types, using rules called the default argument promotions. (Also, if a function is just being declared, not defined, the names of the parameters can be omitted, leaving a prototype that just lists types, as in type function(type, type, type).)
Because of the old grammar, to declare a function with a prototype that says it has no parameters, you need to explicitly say void: type function(void). (This is different in C++, which does not have to support the old grammar. In C++, type function() is a prototype with no parameters.)
Additionally, a prototype can specify that there are variable arguments by putting ,... after one or more regular parameters:
type function(type parameter,...)
In this case, the first arguments are converted to the parameter types, but the arguments that correspond to the ... are converted using the default argument promotions. printf is declared this way.
The default argument promotions are:
Integers narrower (technically, of lesser rank) than int are promoted to int if it can represent all the values of the source type or unsigned int otherwise.
float arguments are converted to double.
There is also some finickiness about bit-fields in the default argument promotions, which I cannot say has ever arisen in code for me.
History
In the old C grammar, a function would be defined with:
type function(name0, name1, name2)
type name0;
type name1;
type name2;
and it would be declared without a prototype, as with type function(). This means the caller did not know the actual types of the parameters. But you could not just pass a char value for a char argument or a short value for a short argument, because C, in trying to be flexible so it could work on many types of computers, had rules about char and short values being promoted to int in expressions. Additionally, character constants like 'X' have type int, not char, so, if somebody called a function with foo('X'), the compiler would not know if foo really wanted just a char rather than an int. So the default argument promotions were made to match the integer promotions.
Later versions of C fixed this by providing a way to declare the argument types in declarations visible to the caller, so the arguments always match the parameters (and the compiler has more information it can use to provide error messages). But the old grammar still has to be supported so that old code can be compiled.
More
The phrase in the C standard, “the expression that denotes the called function,” is used because you can call functions through pointers, not just their names. For example, we can write:
int (*FunctionPointer)() = (int (*)()) printf;
and then call printf using FunctionPointer("Hello, world.\n");. Then “the expression that denotes the called function” is FunctionPointer, and it does not have a prototype even though printf does. (There is no good reason to do this with the printf function, but some esoteric code may do some unsavory things.)
You can initially declare a function without a prototype:
int foo();
and later add a prototype:
int foo(float x, char *y);
The compiler will merge the declarations, and the resulting foo will have a prototype.
The expression that denotes the called function is printf, right.
And if you don't provide a prototype for this function, for example by not including <stdio.h> and not declaring of printf() yourself, the compiler recognizes the function as prototype-less.
In your case the first argument is a const char *, and the second argument is an int.
So, it is not a that has no prototype, but printf().
The value of a will be promoted to int.
The assumed prototype will be int printf(); and takes any number and any type of parameters.

Compatibility of function types that does not include a prototype

There is a rule for function type compatibility N2310 6.7.6.3(p15):
If one type has a parameter type list and the other type is specified
by a function declarator that is not part of a function definition and
that contains an empty identifier list, the parameter list shall not
have an ellipsis terminator and the type of each parameter shall be
compatible with the type that results from the application of the
default argument promotions.
I can imagine an example:
#include <stdio.h>
int foo();
float bar();
int main(void){
printf("%d\n", foo(1, 3)); //fine, int is unchanged by default argument promotion
printf("%f\n", bar(1.0f, 2.0f)); //error, float is promoted to double
}
int foo(int a, int b){
return a + b;
}
float bar(float b, float c){
return b + c;
}
The thing that I found contradictory was that 6.5.2.2(p6) mentions that :
If the number of arguments does not equal the number of parameters,
the behavior is undefined.
In the case of int foo() it has an empty identifier-list. So does a call printf("%d\n", foo(1, 3)); yield UB (2 arguments were supplied)?
Anyway the rules look pretty strange and kind of unnatural. What was the reason for that? I suppose some backward compatibility with previous versions of the Standard... ?
C 2018 6.7.6.3 15 tells you whether two types are compatible. So it can be used to compare two declarations. For foo, you have:
int foo();
int foo(int a, int b) {...}
Of these, the second has a parameter list, and the first is specified by a function declaration that is not part of a function definition and that contains an empty identifier list. So the rule is 6.7.6.3 15 applies. It says the parameter list shall not have an ellipsis terminator (and it does not), and that the type of each parameter shall be compatible with the type that results from the default argument promotions (and they are, since int produces int).
Then, for bar, we have:
float bar();
float bar(float b, float c) {...}
Again, 6.7.6.3 15 applies. But in this case, each parameter has a type that does not result from the default argument promotions, since the default promotions convert float to double. So these two declarations declare bar with incompatible types.
Regarding 6.5.2.2 6:
… If the number of arguments does not equal the number of parameters, the behavior is undefined…
This refers to the number of parameters of the actual function, not the number of parameters appearing in the (empty) list in the declaration.
Anyway the rules look pretty strange and kind of unnatural. What was the reason for that? I suppose some backward compatibility with previous versions of the Standard... ?
Yes, C was originally lax about function declarations, allowing functions to be declared with empty parameter lists, if I recall correctly, all arguments were passed with the promoted types. Support for stricter and more precise declarations came later, and the rules are written to allow old code to continue to work.
Note that the rule about compatibility of function types is relevant for the declarations of the functions, but not the calls.
When a function call is being analyzed by the compiler, the rules in 6.5.2.2 are used to prepare the call. These rules say the arguments are treated in various ways according to the declaration of the function that is visible at the point of the call. (Technically, to the type of the expression that denotes the called function. This is often a function name but could be a pointer to a function, including one computed by a cast expression.)
The rules about compatibility assure you that, if you call a function using a type that is compatible with the type of the actual function definition, then the call has defined behavior.

Does calling printf without a proper prototype invoke undefined behavior?

Does this innocent looking program invoke undefined behavior:
int main(void) {
printf("%d\n", 1);
return 0;
}
Yes invoking printf() without a proper prototype (from the standard header <stdio.h> or from a properly written declaration) invokes undefined behavior.
As documented in the C Standard:
6.5.2.2 Function calls
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.
In more pragmatic words, in the absence of a prototype for printf, the compiler generates the calling sequence as if printf was defined as int printf(const char *, int) which may be quite different and incompatible with the actual implementation of printf in the standard library, defined as int printf(const char restrict *format, ...).
Ancient ABIs were regular enough that this would not cause a problem, but modern (eg 64-bit) ABIs use more efficient calling sequences that make the above code definitely incorrect.
As a consequence, this famous classic C program could fail too, without the #include <stdio.h> or at least a proper prototype for printf:
int main() {
printf("Hello world\n"); // undefined behavior
return 0;
}
C was originally implemented on platforms where passing a variable number of arguments to a function call wouldn't pose any difficulty, and where a function call like foo("Hey", "there", 123); would be processed the same way if the function signature were any of the following:
int foo(char const *p, char const *q, int x);
int foo(char const *p, char const *q, ...);
int foo(char const *p, ...);
On some platforms, however, the number and format of passed arguments must be known in advance; C implementations for such platforms may accommodate this by treating ... as a void*, and having calling code construct and pass the argument of a structure containing the passed arguments. On such platforms, the function call may need to pass p, q, and x as three arguments, pass p, q, and the address of x as three arguments, or build a structure holding q and x, and pass p along with the address of that structure as two arguments.
If a compiler doesn't know where ... belongs in an argument list, it would have no way of knowing how to format the arguments.

Resources