views:

1392

answers:

3

Setup

I have a few questions about the default argument promotions when calling a function in C. Here's section 6.5.2.2 "Function calls" Paragraphs 6, 7, and 8 from the C99 standard (pdf) (emphasis added and broken into lists for ease of reading):

Paragraph 6

  1. 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.
  2. If the number of arguments does not equal the number of parameters, the behavior is undefined.
  3. 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.
  4. 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.

Paragraph 7

  1. 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.
  2. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter. The default argument promotions are performed on trailing arguments.

Paragraph 8

  1. No other conversions are performed implicitly; in particular, the number and types of arguments are not compared with those of the parameters in a function definition that does not include a function prototype declarator.

What I know

  • The default argument promotions are char and short to int/unsigned int and float to double
  • The optional arguments to variadic functions (like printf) are subject to the default argument promotions

For the record, my understanding of a function prototype is this:

void func(int a, char b, float c);  // Function prototype
void func(int a, char b, float c) { /* ... */ }  // Function definition

Question

I'm having a really hard time groking all of this. Here are some questions I have:

  • Do prototyped and non-prototyped functions' behavior really differ so much, such as with regard to default promotions and implicit conversions?
  • When do default argument promotions occur? Is it always? Or is it just in special cases (like with variadic functions)? Does it depend on whether a function is prototyped?
+9  A: 
  • (Non variadic) parameters to functions with a prototype are converted to the corresponding type, which can be char, short, float.

  • Parameters to functions without prototype and variadic parameters are subject to default argument promotions.

If you define a function with a prototype and use it without the prototype or vise versa and it has parameters of type char, short or float, you'll probably have a problem at run-time. You'll have the same kind of problems with variadic functions if the promoted type doesn't match what is used when reading the argument list.

Example 1: problem when defining a function with a prototype and using it without.

definition.c

void f(char c)
{
   printf("%c", c);
}

use.c

void f();

int main()
{
   f('x');
}

can fail because an int will be passed and the function expect a char.

Example 2: problem when defining a function without a prototype and using it with one.

definition.c

void f(c)
   char c;
{
   printf("%c", c);
}

(This is kind of definition is very old fashioned)

use.c

void f(char c);

int main()
{
   f('x');
}

can fail because an int is expected but a char will be passed.

Note: you'll remark that all function from the standard library have types which result from default promotions. So they didn't cause problem during the transition when prototypes were added.

AProgrammer
What do you mean when you say "If you declare a function with a prototype and use it without the prototype..."?
Andrew Keeton
Oh, I used declare when I meant define. Fixed and added an example.
AProgrammer
+7  A: 
Norman Ramsey
Thanks for clarifying. Answering the "why" really helps clear this up for me.
Andrew Keeton
+1  A: 

Your confusion stems from a very slight misunderstanding of the terminology - both declarations and definitions can include prototypes (or not):

void func(int a, char b, float c);

That is a function declaration that includes a prototype.

void func(int a, char b, float c) { /* ... */ }

That is a function definition that includes a prototype.

"Prototyped" and "non-prototyped" are just attributes of a function type, and both declarations and definitions introduce the type of the function.

So you can have a declaration without a prototype:

void func();
caf