Reputation: 23
#include <stdio.h>
int main(void)
{
float a, b;
a = 0.0f, b = 1.0f;
printf("%d\n", plus(a, b));
return 0;
}
int plus(double a, double b)
{
printf("%lf %lf\n", a, b);
return a + b;
}
gcc .\p1.c --std=c89 -o main
the output is:
0.000000 1.000000
1
But when i changed the type from float to int
#include <stdio.h>
int main(void)
{
int a, b;
a = 0, b = 1;
printf("%d\n", plus(a, b));
return 0;
}
int plus(double a, double b)
{
printf("%lf %lf\n", a, b);
return a + b;
}
gcc .\p1.c --std=c89 -o main
the output is:
32019693379112340.000000 622696491526558860000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000000
-2147483648
In the first stage, the default argument promotion changes float to double, and everything is right.
In the second, int -> unsigned int, why does it also call plus function i defined, and why the value of argument in the plus function is out of expectation
I tried to use following to simulate the process:
#include <stdio.h>
int main(void)
{
unsigned int a1, b1;
a1 = 0u, b1 = 1u;
double da = a1;
double db = b1;
printf("%d\n", plus(da, db));
return 0;
}
int plus(double a, double b)
{
printf("%lf %lf\n", a, b);
return a + b;
}
gcc .\p1.c --std=c89 -o main
the output is:
0.000000 1.000000
1
What confuses me even more is this:
#include <stdio.h>
int main(void)
{
unsigned int a1, b1;
a1 = 0u, b1 = 1u;
double da = a1;
double db = b1;
printf("%d\n", plus(da, db));
int a, b;
a = 0, b = 1;
printf("%d\n", plus(a, b));
double x = 3.0;
printf("%d\n", add5(x));
return 0;
}
int plus(double a, double b)
{
printf("%lf %lf\n", a, b);
return a + b;
gcc .\p1.c --std=c89 -o main
the output is:
0.000000 1.000000
1
0.000000 0.000000
0
Upvotes: 2
Views: 129
Reputation: 179392
GCC documents what happens in this case.
From Function Declarations:
The old-fashioned form of declaration, which is not a prototype, says nothing about the types of arguments or how many they should be:
rettype function ();
Warning: Arguments passed to a function declared without a prototype are converted with the default argument promotions (see Argument Promotions. Likewise for additional arguments whose types are unspecified.
[…]
Calling a function that is undeclared has the effect of an creating implicit declaration in the innermost containing scope, equivalent to this:
extern int function ();
This declaration says that the function returns int but leaves its argument types unspecified. If that does not accurately fit the function, then the program needs an explicit declaration of the function with argument types in order to call it correctly.
Implicit declarations are deprecated, and a function call that creates one causes a warning.
So your function call is going to be interpreted as a call to a function that lacks a prototype and returns int. The arguments will be converted according to GCC’s default argument promotions, which essentially will convert integral types narrower than int
to int
, and also convert float
to double
.
So in the first case, you pass (double, double) to plus after conversion, and everything is good.
In the second case, you pass (int, int) to plus after conversion. Since you’re now passing the wrong argument types to a function, the result is undefined behaviour, and anything can happen. In this case, you get nonsense.
Why this specific nonsense, though? That depends on the compiler, compiler settings (e.g. higher optimization levels may lead to more or less nonsensical behaviour) and function call ABI.
Under Microsoft’s standard x64 calling convention, for example, double arguments get passed in the xmm SSE registers (xmm0, xmm1, …) while integral arguments are passed in the general-purpose registers (rcx, rdx, …). So if you’re on Windows running on a 64-bit Intel CPU, or a platform with a similar calling convention, your second case would be passing two integers in rcx, rdx but the function would be reading xmm0 and xmm1 and seeing whatever nonsense happened to be in there.
In fact, the first argument, 32019693379112340.0, is encoded as the bytes 65736b746f705c43
- which happens to read esktop\C
in ASCII. This is very likely to be a fragment of a file path left over in xmm0 by the C startup routine that runs before main().
Upvotes: 4
Reputation: 61910
I guess the answer is in C89 which allows implicit function declarations.
C89 Section 6.3.2.2
If the expression that precedes the parenthesized argument list in a function call consists solely of an identifier. and if no declaration is visible for this identifier, the identifier is implicitly declared exactly as if. in the innermost block containing the function call. the declaration
extern int identifier ();
appeared.[38]
Footnote
- That is. an identifier with block scope declared to have external linkage with type function without parameter information and returning an int If in tact it is not defined as having type "function returning int," the behavior is undefined.
Therefore, essentially because your function int plus (int, int)
does not match extern int identifier ()
, as it does not match the argument list, it is undefined behaviour.
Therefore, if you run it in different systems at different times, you will get different results based on how it is being handled in that corresponding system exactly.
Please note that this is not present in C99 or further standards and implicit function declarations are illegal.
If compile your second code block (in my case with -mno-sse
to just use the floating point unit and not SSE instructions) with implicit declaration and disassemble then it looks like:
0000000000401126 <main>:
401126: 55 push %rbp
401127: 48 89 e5 mov %rsp,%rbp
40112a: 48 83 ec 10 sub $0x10,%rsp
40112e: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
401135: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
40113c: 8b 55 f8 mov -0x8(%rbp),%edx
40113f: 8b 45 fc mov -0x4(%rbp),%eax
401142: 89 d6 mov %edx,%esi
401144: 89 c7 mov %eax,%edi
401146: b8 00 00 00 00 mov $0x0,%eax
40114b: e8 18 00 00 00 call 401168 <plus>
401150: 89 c6 mov %eax,%esi
401152: bf 10 20 40 00 mov $0x402010,%edi
401157: b8 00 00 00 00 mov $0x0,%eax
40115c: e8 cf fe ff ff call 401030 <printf@plt>
401161: b8 00 00 00 00 mov $0x0,%eax
401166: c9 leave
401167: c3 ret
Whereas if you include a function prototype to the same code, compile (in my case with -mno-sse
to just use the floating point unit and not SSE instructions) and disassemble then it will look like:
0000000000401126 <main>:
401126: 55 push %rbp
401127: 48 89 e5 mov %rsp,%rbp
40112a: 48 83 ec 10 sub $0x10,%rsp
40112e: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
401135: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%rbp)
40113c: db 45 f8 fildl -0x8(%rbp)
40113f: db 45 fc fildl -0x4(%rbp)
401142: d9 c9 fxch %st(1)
401144: 48 8d 64 24 f8 lea -0x8(%rsp),%rsp
401149: dd 1c 24 fstpl (%rsp)
40114c: 48 8d 64 24 f8 lea -0x8(%rsp),%rsp
401151: dd 1c 24 fstpl (%rsp)
401154: e8 1c 00 00 00 call 401175 <plus>
401159: 48 83 c4 10 add $0x10,%rsp
40115d: 89 c6 mov %eax,%esi
40115f: bf 10 20 40 00 mov $0x402010,%edi
401164: b8 00 00 00 00 mov $0x0,%eax
401169: e8 c2 fe ff ff call 401030 <printf@plt>
40116e: b8 00 00 00 00 mov $0x0,%eax
401173: c9 leave
401174: c3 ret
Note that when you do not have the function prototype, it does not perform any conversion from the integer to double precision. flid
instruction of the floating point unit does this conversion and takes the contents of the variable from general purpose registers to the floating point registers registers.
Therefore, I think that your variable a = 1
bit pattern is directly interpreted as a double.
Upvotes: 0