C: type conversion when passing an argument on a function call

Solution 1:

Casts are irrelevant, it's the (possibly implicit) prototype that matters.

void foo(short s) {
    // do something
}

int main(void) {
  signed char c = 'a';

  foo(c);  // c is promoted to short by explicit prototype
  bar(c);  // c is promoted to int by implicit prototype
}

void bar(int i) {
    // do something
}

When the book says "an argument of a function call is an expression" it means that the same type promotion rules apply. It might be easier to understand if you think of a function argument as an implicit assignment to the variable specified in the function prototype. e.g. in the call to foo() above there's an implicit short s = c.

This is why casts don't matter. Consider the following code snippet:

signed char c = 'a';
int i = (short) c;

Here the value of c is promoted first to short (explicitly) then to int (implicitly). The value of i will always be an int.

As for char and short becoming int and float becoming double that refers to the default types for implicit function prototypes. When the compiler sees a call to a function before it has seen either a prototype or the definition of the function it generates a prototype automatically. It defaults to int for integer values and double for floating-point values.

If the eventual function declaration doesn't match to implicit prototype, you'll get warnings.

Solution 2:

You've got the general idea of what's wrong, but not exactly.

What happened is that when you wrote

function_call(c, number);

The compiler saw that you were calling a function that it had not seen yet, and so had to decide what its signature should be. Based on the promotion rule you quoted earlier, it promoted char to int and float to double and decided that the signature is

function_call(int, double)

Then when it sees

function_call(char c, float f)

it interprets this as a signature for a different function with the same name, which is not allowed in C. It's exactly the same error as if you prototype a function differently from how you actually define it, just that in this case the prototype is implicitly generated by the compiler.

So, it is this rule that's causing the issue, but the error doesn't have anything to do with actually converting values back and forth between types.

Solution 3:

The compiler is complaining that it assumed function_call is an int returning function as indicated by the standard, and then you tell it is a void function. The compiler won't care about arguments unless you are explicitely declaring them to be different to the actual function. You can pass it no arguments and it won't complain.

You must always declare your functions because this error will go undetected if the function is in other modules. If the function should return any type that could be larger than int such as void* or long, the cast to int in the caller function will most likely truncate it, leaving you with a weird bug.

Solution 4:

Everyone has missed one thing. In ISO C an ISO-syntax prototype overrides default argument promotion.

And in that case the compiler is allowed to generate different code (!) based purely on the style of definition. This gets you K&R compatibility, but you can't always call between the language levels unless you have written the ISO code as K&R expects or modified the K&R code to see the ISO prototypes.

Try it with cc -S -O ...

extern float q; void f(float a) { q = a; }
movl    8(%ebp), %eax
movl    %eax, q

extern float q; void f(a) float a; { q = a; } // Not the same thing!
fldl    8(%ebp)
fstps   q