Calling a function with a float argument modifies caller's state.

David Brown david.brown@hesbynett.no
Tue Aug 21 21:38:00 GMT 2012


On 21/08/12 19:40, Jeff B wrote:
>
> Thank you for your replies. My responses are below.
>
>
>>>> On 20/08/2012 22:21, Jeff B wrote:
>>>>
>>>> (...)
>>>>
>>>> WHY is a 32-bit float passed to a function in a manner different from a
>>>> 32-bit int???
>>> On 21/08/12 08:40, David Brown wrote:
>>>
>>> A 32-bit float and a 32-bit int are completely different things.
>
> I am completely aware of that.
>
>
>>> The
>>> fact that the have a common size is just coincidence. The compiler
>>> can treat them totally differently -
>
> Obviously it is treating them very differently.
>
>
>>> on many targets (including, I
>>> believe, the x86 - depending on the compiler flags) they will be
>>> passed around in different registers. If it suits the cpu in
>>> question, the compiler can happily convert the 32-bit float to a
>>> 64-bit (or even higher) resolution before passing it around. You
>>> certainly cannot rely on being able to convert randomly between ints
>>> and floats while keeping the same exact values.
>
> I am completely aware of the limitations of conversions.
> That is not the issue.
>
>
>>> When passing data into printf(), the compiler can take the actual
>>> format string into account.
>
> I am aware of that too. I am not concerned that the output might
> be garbled.
>
>
>
>>> When you write "printf("%f", x)", you are
>>> /not/ saying "pass a 32-bit value x, treat it as representing a float,
>>> and print it".
>
> I considered that too, but that is not the issue. I can understand
> that the value might get printed out in some way I didn't expect.
>
>
>>> The compiler may /choose/ to treat it that way. But
>>> it can also validly interpret it as "treat the value x as a float, and
>>> print it out". When you use C properly, according to the
>>> specifications of printf, these do the same thing - but one way might
>>> do it with smaller or faster code, or with better static error
>>> checking. When you break C rules by your mixed casting, they can mean
>>> different things.
>>>
>>>> I also think that it is a really bad idea to have undocumented side
>>>> effects
>>>> like that. Or did I already say that?
>>>>
>>> I think most people will agree that undocumented side effects are a
>>> bad idea. But your program does not demonstrate undocumented side
>>> effects.
>
> Well, a variable I am able to access from main() is changing
> even tho there is nothing in main() itself which is changing it.

It is perhaps a matter of semantics, but you have not demonstrated that 
your calls to printf are changing main's variables in any way.  The 
effect you are seeing is consistent with the theory that the compiler is 
making assumptions based on the types of the variables and their uses, 
that would be correct if the C code were legal - but are causing strange 
effects on your undefined code.

> I do not change the variables after I init them:
>
> floatVarA = 1234.0;
> floatVarB = 5678.0;
>
>
> (int)floatVarA = 0.000000 <<=== initial value 0??
> floatVarA = 1234.000000
> (int)floatVarA = 1234.000000 <<=== changed
> floatVarB = 5678.000000
> (int)floatVarA = 5678.000000 <<=== changed again
> floatVarA = 1234.000000
>
>
> The only thing I am doing is calling printf() and passing the
> variables by value. Neither am I using any printf() format
> which is going to send data back to main(), eg %n. So why
> is something visible to main() changing, however esoteric?

(Assuming my theory here is correct...)
The values are /not/ changing - the compiler is generating code that 
would be correct if your code had been legal C.

One of the most obvious possibilities here is that the compiler is 
putting floats into MMX registers, and integers into integer registers. 
  When you call printf with a "%f" parameter, it prints out the value 
that is in the appropriate MMX register - but because you have been 
passing "int" data, it has been passed in an integer register.

You can see that the variables have not been changed or corrupted 
because when you print them out legally (without the casts), you get the 
expected results.

>
> BTW I was originally putting values into a small array of
> doubles (to force alignment) so that I could print them out
> in hex, which is not an unreasonable thing to do. Then I tried

It /is/ an unreasonable thing to do, if you are trying to cast like 
this.  You need to use a type-punning union to convert the data safely, 
or use an memcpy().

> to print the actual values with proper interpretation from
> said buffer using the proper printf() format and with proper
> casts, which is also not an unreasonable thing to do. Casting
> as float with printf() format %f failed that exercise, outputting
> "-nan".
>
> When I started poking at it that's when I discovered that passing
> a float variable (or float constant) was causing a value to change
> in main(). The equivalent code with the same sized ints does not
> exhibit that behavior.
>

It is legal to cast same-sized ints like this, so the code works as you 
want.

>
>>> Your program demonstrates that when you write code that has undefined
>>> behaviour, it does not work as you expect. That should not be a big
>>> surprise.
>
> I agree about using code with undefined behavior. Regardless
> of whatever confusion printf() may have about how to actually
> print something, it seems to me that (int)floatVar ought to be
> getting the bits from the same place as (float)floatVar. Is that
> not true?

No, it is not true - which is the heart of your problem.  Integer 
registers and floating point registers can be completely independent. 
And even if the data is in memory, the compiler can assume they are 
independent because integers cannot alias floats.

>
> Yet clearly the bits are being fetched from elsewhere. When I
> print floatVarA as a float its value is correct and unchanging.
> When I cast that same 32-bit variable to int the code is clearly
> looking at something which is changing and which therefore
> must be something other than the 32 floatVarA bits which are
> demonstrably NOT changing.

True.

>
> Maybe that is the issue. Maybe (int)floatVarA IS looking at
> something outside of the scope of main() which might reasonably
> be expected to change. But if that is the case then the cast is going
> to the wrong place. The variable, however cast, should be fetched
> from the same location in memory whatever confusion might then
> result in the interpretation of the fetched bits. Yes?

No.  (See above.)

>
>
>>>
>>> You also don't demonstrate that the calls to printf are modifying the
>>> caller's state.
>
> (int)floatVarA = 0.000000 <<=== initial value 0??
> floatVarA = 1234.000000
> (int)floatVarA = 1234.000000 <<=== changed
> floatVarB = 5678.000000
> (int)floatVarA = 5678.000000 <<=== changed again
> floatVarA = 1234.000000
>

As noted before, this does not demonstrate that the caller's state is in 
any way modified - only that your undefined printf calls do not work in 
the way you expect (while the legal ones do work as intended).

>>> My immediate interpretation of your result is that
>>> the compiler is doing alias analysis - it knows what can and cannot be
>>> legally done in conversions between unrelated types (int and float),
>
> Again I am not concerned with the misinterpretation of the bits,
> just which location are those bits coming from.
>
>
>>> and it uses this knowledge to generate code that is more optimal under
>>> the assumption that the programmer is following the rules of C. When
>>> you break those rules, you break that assumption - lie to your
>>> compiler and you will get bad code as a result.
>>>
>>>
>>> Try compiling your code with -Wall, which will help point out your
>>> errors.
>> On 08/21/2012 10:20 AM, Ángel González wrote:
>>
>> Compiling with -Wall it will complain about passing an int to a %f
>> parameter.
>> The program shown by Jeff is consistent with printf(..., float) moving
>> the value to a float register and printf("%f") printing the code from
>> that register.
>
> I guess, in summary, my question is now, "Does re-casting a variable
> ever cause data (however misinterpreted) to be fetched from a different
> location?" I should think it would not.
>

Sorry to shatter your illusions!

> Again, thank you for your replies.
>
> Jeff Barry
>
>
> PS: My eMail address, a.GNUbie@myLetters.US, is meant to imply that
> I am new to the specifics of GNU. I am hardly new to low level coding
> in case my eMail address implied that to you.
>

If you are familiar with x86 assembly code, have a look at the assembly 
listing.  It might help you get a feel for what is going on here.

Also remember that being experienced at low-level coding doesn't mean 
you haven't missed something.  A lot of people who had been coding for 
years or decades (such as myself) get caught out by the alias analysis 
done by more modern compilers - indeed it is the low-level experts who 
use these sort of casting tricks that get caught by smarter compilers. 
I certainly know I have written code in the past that would not work on 
a modern gcc because of this sort of thing.



More information about the Gcc-help mailing list