Default argument promotions according to C standards - c

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.

Related

Default argument and parameter promotions in 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

Definition of `int foo() {}` vs `int foo(void) {}` following declaration of `int foo(void);` [duplicate]

This question already has answers here:
func() vs func(void) in C99
(4 answers)
Closed 2 years ago.
Note: this is not the same as func() vs func(void) in c99, because the question here specifically asks about the implementation of a zero-argument function following a valid declaration.
Should the implementation of a zero-argument include the void keyword? Specifically, does the C standard have anything to say about the implementation of the following two functions? Note that both foo1 and foo2 are declared as zero-argument functions; the only difference is in the implementation, not in the declaration:
#include <stdio.h>
int foo1(void); // inform compiler that foo1 and foo2 are zero-args fns.
int foo2(void);
int main() {
printf("%d\n", foo1());
printf("%d\n", foo2());
return 0;
}
int foo1(void) { return 22; }
int foo2() { return 22; }
I note that gcc -Wall -std=c99 -Wpedantic foo.c -o foo compiles and executes without any warnings or errors, but is there any violation of the standard going on?
The code you have posted is correct. int foo2(void); declares foo2 as taking no arguments, and forms a prototype.
The function definition must be compatible with this; and a definition with empty parentheses is compatible with this prototype. This is specified in C11 6.7.6.3/15, which is a mouthful:
For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. 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. If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument
promotions to the type of the corresponding identifier. (In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)
The reason there's so much text on this point is that C originally only had K&R style functions, and then prototypes were added . So there has to be text to cover all possible combinations of K&R style mixed with prototype style . The section beginning with my bolded part refers to using a K&R-style function definition when the function has previously been declared with a prototype.
Also relevant: use of empty parentheses is obsolescent in C11 (6.11.6).
Certain features are obsolescent, which means that they may be considered for
withdrawal in future revisions of this International Standard. They are retained because of their widespread use, but their use in new implementations or new programs is discouraged.

What are the semantics of function pointers with empty parentheses in each C standard?

Answers to this and this question say that function pointers of the form return-type (*pointer)() are pointers to a function which takes any number of arguments, though the latter says they obsolesced in C11.
On an i386 system with GCC, “extra” arguments passed in a call to an empty-parentheses-type’d function pointer are ignored, because of how stack frames work; e.g.,
/* test.c */
#include <stdio.h>
int foo(int arg) { return arg; }
int main(void)
{
int (*fp)() = foo;
printf("%d\n", fp(267239151, 42, (struct { int x, y, z; }){ 1, 2, 3 }));
return 0;
}
$ gcc -o test test.c && ./test
267239151
$
In which C standards are empty-parentheses’d function pointers allowed? and wherever so, what are they specified to mean?
N1570 6.11.6:
The use of function declarators with empty parentheses (not
prototype-format parameter type declarators) is an obsolescent
feature.
This same wording appears in the 1990, 1999, and 2011 editions of the ISO C standard. There has been no change. The word obsolescent says that the feature may be removed in a future edition of the Standard, but so far the committee has not done so. (Function pointer declarations are just one of several contexts where function declarators can appear.)
The Introduction section of the C standard explains what obsolescent means:
Certain features are obsolescent, which means that they may be
considered for withdrawal in future revisions of this International
Standard. They are retained because of their widespread use, but their
use in new implementations (for implementation features) or new
programs (for language [6.11] or library features [7.31]) is
discouraged.
A call to a function declared with an old-style declarator is still required to pass the correct number and type(s) of arguments (after promotion) as defined by the function's actual definition. A call with incorrect arguments has undefined behavior, which means that the compiler is not required to diagnose the error; the burden is entirely on the programmer.
This is why prototypes were introduced, so that the compiler could check correctness of arguments.
On an i386 system with GCC, “extra” arguments passed in a call to an
empty-parentheses-type’d function pointer are ignored, because of how
stack frames work ...
Yes, that's well within the bounds of undefined behavior. The worst symptom of undefined behavior is having the program work exactly as you expect it to. It means that you have a bug that hasn't exhibited itself yet, and it will be difficult to track it down.
You should not depend on that unless you have a very good reason to do so.
If you change
int (*fp)() = foo;
to
int (*fp)(int) = foo;
the compiler will diagnose the incorrect call.
Any function declarator can have empty parentheses (unless it's a function declaration where there is already a non-void prototype in scope). This isn't deprecated, although it is "obsolescent".
In a function pointer, it means the pointer can point to a function with any argument list.
Note that when actually calling a function through the pointer, the arguments must be of correct type and number according to the function definition, otherwise the behaviour is undefined.
Although C allows you to declare a function (or pointer to function) with an empty parameter list, that does not change the fact that the function must be defined with a precise of parameters, each with a precise type. [Note 1]
If the parameter declaration is not visible at a call site, the compiler will obviously not be able to perform appropriate conversions to the provided arguments. It is, therefore, the programmer's responsibility to ensure that there are a correct number of arguments, all of them with the correct type. For some parameter types, this will not be possible because the compiler will apply the default argument promotions. [Note 2]
Calling a function with an incorrect number of arguments or with an argument whose type is not compatible with the corresponding parameter type is Undefined Behaviour.
The fact that the visible declaration has an empty parameter list does not change the way the function is called. It just puts more burden on the programmer to ensure that the call is well-defined.
This is equally true of pointer to function declarations.
In short, the sample code in the question is Undefined Behaviour. It happens to "work" on certain platforms, but it is neither portable nor is it guaranteed to keep working if you recompile. So the only possible advice is "Don't do that."
If you want to create a function which can accept extra arguments, use a varargs declaration. (See open for an example.) But be aware of the limitations: the called function must have some way of knowing the precise number and types of the provided arguments.
Notes
With the the exception of varargs functions, whose prototypes end with .... But a declaration with an empty parameter list cannot be used to call a varargs function.
Integer types narrower than int are converted to int and float values to double.

Function Syntax in C

I'm pretty new to C, I've been studying someone else's code and I've found this function:
static
cp_file(output, input, record, ft)
dy_char *output; /* output file */
dy_char *input; /* input file */
dy_char *record; /* record id */
int ft; /* file type */
{
Does this do exactly the same thing as saying this:
static cp_file(dy_char *output, dy_char *input, dy_char *record, int ft) {
Is one more efficient than the other, or is it purely a different style of syntax? If they are different, what are the differences?
No, they are not identical.
The first form is the old-style function definition and the second is the prototype form of function definition.
They differ with respect to the argument passing conversion. Functions with prototype convert arguments as if by assignment while non-prototype functions like in your first example perform default argument promotion.
Arguments conversion for the old form:
(C99, 6.5.2.2p6) "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."
Arguments conversion for the prototype form:
(C99, 6.5.2.2p7) "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."
Note that the old form (non-prototype) is obsolescent and is strongly discouraged.
(C99, 6.11.7p1 Future languages directions) "The use of function definitions with separate parameter identifier and declaration lists (not prototype-format parameter type and identifier declarators) is an obsolescent feature."
Both are identical, However,
the latter is the standard conformant syntax, while the former is the obscure K&R syntax.
Always, use the standard conformant way when writing any new code, old code bases might use the K&R syntax as it was commonly used syntax at that time.
Yes, both statements are mostly identical except as noted by ouah. This is the "original" pre-ANSI syntax for declaring functions. It went into disuse in the early 1990's.

Why is this legal in C?

I am writing a toy C compiler for a compiler/language course at my university.
I'm trying to flesh out the semantics for symbol resolution in C, and came up with this test case which I tried against regular compilers clang & gcc.
void foo() { }
int main() { foo(5); } // foo has extraneous arguments
Most compilers only seem to warn about extraneous arguments.
Question: What is the fundamental reasoning behind this?
For my symbol table generation/resolution phase, I was considering a function to be a symbol with a return type, and several parametrized arguments (based on the grammar) each with a respective type.
Thanks.
A function with no listed arguments in the prototype is deemed to have an indeterminate number, not zero.
If you really want zero arguments, it should be:
void foo (void);
The empty-list variant is a holdover from ancient C, even before ANSI got their hands on it, where you had things like:
add_one(val)
int val;
{
return val + 1;
}
(with int being the default return type and parameter types specified outside the declarator).
If you're doing a toy compiler and you're not worried about compliance with every tiny little piece of C99, I'd just toss that option out and require a parameter list of some sort.
It'll make your life substantially easier, and I question the need for people to use that "feature" anyway.
It's for backward compatibility with ancient C compilers. Back before the earth cooled, all C function declarations looked roughly like:
int foo();
long bar();
and so on. This told the compiler that the name referred to a function, but did not specify anything about the number or types of parameters. Probably the single biggest change in the original (1989) C standard was adding "function prototypes", which allowed the number and type(s) of parameters to be declared, so the compiler could check what you passed when you called a function. To maintain compatibility for existing code, they decided that an empty parameter list would retain its existing meaning, and if you wanted to declare a function that took no parameters, you'd have to add void in place of the parameter list: int f(void);.
Note that in C++ the same is not true -- C++ eliminates the old style function declarations, and requires that the number and type(s) of all parameters be specified1. If you declare the function without parameters, that means it doesn't take any parameters, and the compiler will complain if you try to pass any (unless you've also overloaded the function so there's another function with the same name that can take parameters).
1 Though you can still use an ellipsis for a function that takes a variable parameter list -- but when/if you do so, you can only pass POD types as parameters.
You haven't provided a prototype for the foo function, so the compiler can't enforce it.
If you wrote:
void foo(void) {}
then you would be providing a prototype of a function that takes no parameters.
gcc's -Wstrict-prototypes will catch this. For an error, use -Werror=strict-prototypes. The standard never specifies whether something should be a warning or an error.
Why is this legal in C?
First just to clarify the C Standard does not use the word legal.
In the C terminology, this program is not strictly conforming:
void foo() { }
int main() { foo(5); } // foo has extraneous arguments
When compiling this program, no diagnostic is required because of the function call foo(5): there is no constraint violation. But calling the function foo with an argument invokes undefined behavior. As any program that invokes undefined behavior, it is not strictly conforming and a compiler has the right to refuse to translate the program.
In the C Standard, a function declaration with an empty list of parameters means the function has an unspecified number of parameters. But a function definition with an empty list of parameters means the function has no parameter.
Here is the relevant paragraph in the C Standard (all emphasis mine):
(C99, 6.7.5.3p14) "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 paragraph of the C Standard that says the foo(5) call is undefined behavior is this one:
(C99, 6.5.2.2p6) "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."
And from (C99, 6.9.1p7), we know the definition of foo does not provide a prototype.
(C99, 6.9.1p7) "If the declarator includes a parameter type list, the list also specifies the types of all the parameters; such a declarator also serves as a function prototype for later calls to the same function in the same translation unit. If the declarator includes an identifier list,the types of the parameters shall be declared in a following declaration list."
See the Committee answer to the Defect Report #317 for an authoritative answer on the subject:
http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_317.htm

Resources