Function calls n1256 6.5.2.2 p5, p6 - c

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.
}

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.

Can I call a function taking a long parameter with an int argument?

Is this code undefined behavior?
extern long f(long x);
long g(int x)
{
return f(x);
}
According to the C11 standard, in 6.5.2.2 §6:
If the function is defined with a type that includes a prototype, and [...] the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined.
In the example above, the function f is defined with a type that includes a prototype and the type of the argument x is int while the type of the parameter x is long. According to 6.2.7 §1:
Two types have compatible type if their types are the same.
Therefore, long and int are not compatible, so the behavior is undefined, right?
However, in 6.5.2.2 §7:
If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type.
If I correctly understand this paragraph, it means the argument x which is of type int is implicitly converted to long when the function is called. According to 6.3.1.3 §1:
When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
As int has a lower rank than long, every int variable can be represented by a long variable. Therefore, the argument x can be converted into a long. Therefore, this is not undefined behavior.
Which interpretation of the standard is right? Is my code undefined behavior or not?
You provided irrelevant quotes relative to your code snippet. According to the same section (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. Each argument shall have a type such that its
value may be assigned to an object with the unqualified version of the
type of its corresponding parameter.
The function f has a prototype that is visible in the call expression
extern long f(long x);
and this assignment
int argument;
long parameter;
parameter = argument
is correct.
As for this quote
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:
Then it means the following. The function calling expression does not see the function prototype. So the default argument promotions are performed. But somewhere else the function is defined with a function prototype and the promoted arguments are not compatible with function parameters. In this case you will have undefined behavior.
Here is a demonstrative program with undefined behavior related to a function call. The compiler can issue an error message.
#include <stdio.h>
void f();
int main(void)
{
short x = 10;
f( x );
return 0;
}
void f( char *s )
{
printf( "s = %s\n", s );
}
or
#include <stdio.h>
#include <limits.h>
void f();
int main(void)
{
unsigned int x = UINT_MAX;
f( x );
return 0;
}
void f( int x )
{
printf( "x = %hd\n", x );
}
For example in the last program the argument x in the call expression
f( x );
is promoted to the type unsigned int. But according to the function definition the function expects an argument of the type signed int and the passed value can not be stored in the type signed int. So the behavior is undefined.
But your original example of a function call is not related to this quote.
The part "the arguments after promotion" is confusing, it refers to the default argument promotions defined earlier on in that same paragraph. Which doesn't apply here, since those rules are only used when there is no prototype or when we have variadic functions.
So "arguments after promotion are not compatible with the types of the parameters" applies to cases where you don't have a prototype, apply the default argument promotions (integer promotion in case of integers) and if the types are not compatible then, there is undefind behavior.
But since you have a prototype, forget about default argument promotion, instead continue to read the next part, C17 6.5.2.2/7 emphasis mine:
If the expression that denotes the called function has a type that does include a prototype, the arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type.
Then we go read what's said about "as if by assignment", C17 6.5.16 emphasis mine:
the left operand has atomic, qualified, or unqualified arithmetic type, and the right has arithmetic type;
Both int and long are arithmetic types (and there are no qualifiers), this is a valid form of assignment. Further down in the same chapter:
The type of an assignment expression is the type the left operand would have
after lvalue conversion.
So basically the code passing the parameter is equivalent to simple assignment:
int x;
long y;
y = x;
If we let the standard send us further on this merry chase, next look up lvalue conversion, C17 6.3.2.1:
...an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion.
And then the actual conversion for integer types, C17 6.3.1.3:
When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or
subtracting one more than the maximum value that can be represented in the new type
until the value is in the range of the new type.
Otherwise, the new type is signed and the value cannot be represented in it; either the
result is implementation-defined or an implementation-defined signal is raised.
A long can always hold the value of an int, so the first sentence is the conversion that applies in this case.
Code is correct.
IMO, the first interpretation does not a apply.
It is actually referring to calling a function without a prototype that is defined with a prototype:
long g(int x)
{
return f(x);
}
// other translation unit
long f(long x) {
return 0;
}
The code is defined only if f is called with single argument of type compatible with long.
In a typical C implementation where compilation units are processed independently, and the linker combines the separately-compiled object files and performs address relocation without attempting other optimization, there will be a specification, which today would commonly be called the Application Binary Interface, which describes among other things where/how code which calls a function should store the arguments, and where/how a function should expect to find arguments stored by its caller.
If a function attempts to retrieve arguments in a manner inconsistent with how its caller had stored them, the results are not likely to be meaningful. On the other hand, many platform ABIs describe behavior in terms of storage formats, rather than C data types. Thus, on e.g. a 32-bit ARM implementation where both int and long are 32-bit data types, a function that expects an int would be called in exactly the same fashion as one that expects a long or an int32_t. An ARM implementation that processes the code in different compilation units independently would thus not need to care if one compilation unit uses type int and another uses long, provided that both types have the same representation.
In general, one should make parameter/argument types match when practical, even on implementations that wouldn't care about such things, because it will make it easier for people to read the code and know what it's doing. In some situations, however, one may have different compilation units that expect to be given pointers to functions whose arguments are of different types with matching representations. In such situations, if one is using an implementation that processes compilation units separately, it may be possible to pass the address of a single function to code in both compilation units. Unfortunately, there is no means by which a programmer can indicate when a function call needs to be handled in a fashion consistent with the ABI, without regard for whether the Standard would define its behavior, and some aggressive optimizers make no attempt to meaningfully process constructs whose behavior would be defined by the ABI but not the Standard.

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.

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

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.

Resources