Questions about C Function Prototypes and Compilat

2020-02-12 08:51发布

问题:

With the following code:

int main(){
    printf("%f\n",multiply(2));
    return 0;
}

float multiply(float n){
    return n * 2;
}

When I try to compile I get one warning: "'%f' expects 'double', but argument has type 'int'" and two errors: "conflicting types for 'multiply'", "previous implicit declaration of 'multiply' was here."

Question 1: I am guessing that it's because, given the compiler has no knowledge of function 'multiply' when he comes across it the first time, he will invent a prototype, and invented prototypes always assume 'int' is both returned and taken as parameter. So the invented prototype would be "int multiply(int)", and hence the errors. Is this correct?

Now, the previous code won't even compile. However, if I break the code in two files like this:

#file1.c
 int main(){
    printf("%f\n",multiply(2));
    return 0;
 }

#file2.c
float multiply(float n){
    return n * 2;
}

and execute "gcc file1.c file2.c -o file" it will still give one warning (that printf is expecting double but is getting int) but the errors won't show up anymore and it will compile.

Question 2: How come when I break the code into 2 files it compiles?

Question 3: Once I run the program above (the version split into 2 files) the result is that 0.0000 is printed on the screen. How come? I am guessing the compiler again invented a prototype that doesn't match the function, but why is 0 printed? And if I change the printf("%f") to printf("%d") it prints a 1. Again, any explanation of what's going on behind the scenes?

Thanks a lot in advance.

回答1:

So the invented prototype would be "int multiply(int)", and hence the errors. Is this correct?

Absolutely. This is done for backward compatibility with pre-ANSI C that lacked function prototypes, and everything declared without a type was implicitly int. The compiler compiles your main, creates an implicit definition of int multiply(int), but when it finds the real definition, it discovers the lie, and tells you about it.

How come when I break the code into 2 files it compiles?

The compiler never discovers the lie about the prototype, because it compiles one file at a time: it assumes that multiply takes an int, and returns an int in your main, and does not find any contradictions in multiply.c. Running this program produces undefined behavior, though.

Once I run the program above (the version split into 2 files) the result is that 0.0000 is printed on the screen.

That's the result of undefined behavior described above. The program will compile and link, but because the compiler thinks that multiply takes an int, it would never convert 2 to 2.0F, and multiply will never find out. Similarly, the incorrect value computed by doubling an int reinterpreted as a float inside your multiply function will be treated as an int again.



回答2:

An unspecified function has a return type of int (that's why you get the warning, the compiler thinks it returns an integer) and an unknown number of unspecified arguments.

If you break up your project in multiple files, just declare a function prototype before you call the functions from the other files, and all will work fine.



回答3:

Question1:

So the invented prototype would be "int multiply(int)", and hence the errors. Is this correct?

Not exactelly yes because it it depends of your Cx (C89, C90, C99,...)

for function return values, prior to C99 it was explicitly specified that if no function declaration was visible the translator provided one. These implicit declarations defaulted to a return type of int

Justification from C Standard (6.2.5 page 506)

Prior to C90 there were no function prototypes. Developers expected to be able to interchange argu-ments that had signed and unsigned versions of the same integer type. Having to cast an argument, if the parameter type in the function definition had a different signedness, was seen as counter to C’s easy-going type-checking system and a little intrusive. The introduction of prototypes did not completely do away with the issue of interchangeability of arguments. The ellipsis notation specifies that nothing is known about the 1590 ellipsis supplies no information expected type of arguments. Similarly, for function return values, prior to C99 it was explicitly specified that if no function declaration was visible the translator provided one. These implicit declarations defaulted to a return type of int . If the actual function happened to return the type unsigned int , such a default declaration might have returned an unexpected result. A lot of developers had a casual attitude toward function declarations. The rest of us have to live with the consequences of the Committee not wanting to break all the source code they wrote. The interchangeability of function return values is now a moot point, because C99 requires that a function declaration be visible at the point of call (a default declaration is no longer provided)

Question 2:

How come when I break the code into 2 files it compiles?

it will compile and it will be treated like indicated in the first question exactelly the same



回答4:

Question 1: Yes you are correct. If there is no function prototype, the default type is int

Question 2: When you are compiling this code as one file, the compiler see that there is already function named multiply and it has a different type than supposed (double instead of int). Thus compilation doesn't work.

When you separate this in two files, the compiler makes two .o files. In the first one it suppose that the multiply() function will be in other file. Then linker links both files into a binary and according to the name multiply inserts call of float multiply() on the place of int multiply() supposed by the compiler in the first .o file.

Question 3: If you read int 2 as a float, you will get a very small number (~1/2^25), so after that you multiply it by 2 and it still remains too small for format %f. That's why you see 0.00000.