-4

I have read Garbage value when passed float values to the function accepting integer parameters answers. My question goes a bit deeper. I could have also asked there had I more than 50 reputation point. I am adding my code for more clarification:

#include <stdio.h>
#include <string.h>

void p2(unsigned int tmp)
{
    printf("From p2: \n");
    printf("tmp = %d ,In hex tmp = %x\n", tmp, tmp);
}

int main()
{
    float fvar = 45.65;

    p1(fvar);
    p2(fvar);
    printf("From main:\n");
    printf("sizeof(int) = %lu, sizeof(float) = %lu\n", sizeof(int),
            sizeof(float));
    unsigned int ui;
    memcpy(&ui, &fvar, sizeof(fvar));
    printf("fvar = %x\n", ui);
    return 0;
}

void p1(unsigned int tmp)
{
    printf("From p1: \n");
    printf("tmp = %d ,In hex tmp = %x\n", tmp, tmp);
}

The output is:

From p1: 
tmp = 1 ,In hex tmp = 1
From p2: 
tmp = 45 ,In hex tmp = 2d
From main:
sizeof(int) = 4, sizeof(float) = 4
fvar = 4236999a8

Passing a float value to a function that is declared beforehand (i.e. p2) with int arguments gives the correct result. When trying the same with a function that is not declared beforehand (i.e. p1) gives incorrect values. And I know the reason that compiler won't assume any type or arity of arguments for the function not declared before handed. That's why float value does not get typecasted to int in the case of p2.

My confusion is, in the case of p2, how exactly does float value get copied to local int variable tmp.

If it is 'bit by bit copy' than reading those locations should yield something (except 1) in hex at least (if not in integer). But that does not sound the case as output shows. I know that float representation is different.

And how p2 may read registers/stack locations that floats weren't copied to? as simonc suggested in the linked question?

I have included the size of int and float both and my compiler is gcc if that helps.

Community
  • 1
  • 1
UserXYZ
  • 53
  • 1
  • 9
  • 7
    it's Undefined Behavior. Don't try to reason it out. By definition it's not defined what happens. – bolov Mar 02 '17 at 07:57
  • 1
    If you want to define a function after the caller function **you must** add a prototype before the caller function. That's all – LPs Mar 02 '17 at 07:58
  • 1
    To elaborate what @LPs said, without that , your code is not a _valid_ C code, anymore. – Sourav Ghosh Mar 02 '17 at 07:59
  • 1
    Moreover take a look at [compiler errors](http://ideone.com/ZSj5fI) – LPs Mar 02 '17 at 08:00
  • You are trying to call a function that has not been declared, so it's automatically declared as `int p1()`. Even though you later define it as `void p1(unsigned int tmp)`, it has already been declared as `int p1()` (not taking any parameters). I'm pretty sure the compiler is screaming with warnings and errors about that, those errors aren't meant to be ignored. – Havenard Mar 02 '17 at 08:18
  • Thank you for your suggestions. @Antti Haapala's answer explains it all. – UserXYZ Mar 02 '17 at 10:26

3 Answers3

3

The C programming language is essentially a single-scan language - a compiler doesn't need to reread the code but it can assemble it line by line, retaining information only on how identifiers were declared.

The C89 standard had the concept of implicit declaration. In absence of a declaration, the function p1 is declared implicitly as int p1(); i.e. a function that returns an int and takes unspecified arguments that go through default argument promotions. When you call such a function giving it a float as an argument, the float argument is promoted to a double, as called for by default argument promotions. It would be fine if the function was int p1(double arg); but the expected argument type is unsigned int, and the return value is not compatible either (void vs int). This mismatch will cause the program to have undefined behaviour - there is no point in reasoning what is happening then. However, there are many old C programs that would fail to compile, if the compilers wouldn't support the archaic implicit declarations - thus you just need to consider all these warnings as errors.

Notice that if you change the return value of p1 into an int, you will get less warnings:

% gcc implicit.c
implicit.c:14:5: warning: implicit declaration of function ‘p1’ [-Wimplicit-function-declaration]
 p1(fvar);
 ^~

But the observed behaviour on my compiler would be mostly the same.

Thus the presence of mere warning: implicit declaration of function ‘x’ is quite likely a serious error in newly written code.

Were the function declared before its use, as is case with p2, then the compiler knows that it expects an unsigned long as the argument, and returns void, and therefore it would know to generate correct conversion code from float to unsigned long for the argument.


The C99 and C11 do not allow implicit function declarations in strictly-conforming programs - but they also do not require a conforming compiler to reject them either. C11 says:

An identifier is a primary expression, provided it has been declared as designating an object (in which case it is an lvalue) or a function (in which case it is a function designator).

and a footnote noting that

Thus, an undeclared identifier is a violation of the syntax.

However, it doesn't require a compiler to reject them.

  • I got it. It's undefined behavior. I like your explanation. And I got a question: Why C standard allows it? Shouldn't this undefined behavior give ERROR in the first place (even without using -Werror)? Or is it useful in any case? – UserXYZ Mar 02 '17 at 10:19
  • "However, there are many old C programs that would fail to compile, if the compilers wouldn't support the archaic implicit declarations - thus you just need to consider all these warnings as errors." – Antti Haapala -- Слава Україні Mar 02 '17 at 11:40
  • It is just the case that implicit function declarations was how it was done in the 70s :D Read more about implicit function declarations [here](http://stackoverflow.com/questions/9182763/implicit-function-declarations-in-c). Note that the question has doubly-undefined behaviour because it is redefining the built-in function that too has undefined behaviour. – Antti Haapala -- Слава Україні Mar 02 '17 at 11:40
2

This,

void p1(unsigned int tmp);

would be implicitly declared as

int p1();

by the compiler.

Although the compiler does not throw an error, it should be considered one as you can read in the linked post.

In any event, this is undefined behavior and you can't expect a predictable output.

Community
  • 1
  • 1
Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
0

In binary level, float and int don't look alike at all.

When trying to copy a float into a int, there's an implicit conversion, that's why when you call a function that takes int as argument but you provide a float you get the integer part of it, but in the final test you get to see how ugly it really look like. That's no garbage, that's how a float looks like in memory if you'd print it in hexadecimal. See IEEE 754 for details.

The issue with p1() however is that you are trying to call a function that has not been declared, so it's automatically declared as int p1(). Even though you later define it as void p1(unsigned int tmp), it has already been declared as int p1() (not taking any parameters) so it doesn't work (behavior is undefined). I'm pretty sure the compiler is screaming with warnings and errors about that, those errors aren't meant to be ignored.

Notice there's a big difference between declaring and defining a function. It is perfectly legal to define a function later, the way you are doing, but if you want it to work properly it has to be declared before any attempt to use it.

Example:

// declare functions
void p1(unsigned int tmp);
void p2(unsigned int tmp);

// use functions
int main()
{
    p1(1);
    p2(1);
}

// define functions
void p1(unsigned int tmp)
{
    // do stuff
}
void p2(unsigned int tmp)
{
    // do stuff
}
Havenard
  • 27,022
  • 5
  • 36
  • 62