LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Software (http://www.linuxquestions.org/questions/linux-software-2/)
-   -   Passing a float variable to a function in C modifies caller's state. (http://www.linuxquestions.org/questions/linux-software-2/passing-a-float-variable-to-a-function-in-c-modifies-callers-state-4175423119/)

GreyBeard 08-20-2012 04:02 PM

Passing a float variable to a function in C modifies caller's state.
 
Hi,

I've run into a problem which appears to be doing an undocumented change
of state in the caller's space when passing float variables to a function,
printf(), which should change nothing in the caller's space. Undocumented
side effects in the caller's space strike me as being very bad.

printf() here is not at fault. It is the passing of a float variable which
is at fault. I am compiling with GCC under Linux. There is some weirdness
going on which I do not understand concerning how floats are passed. See

http://en.wikipedia.org/wiki/X86_cal...ventions#cdecl

I originally ran into this problem when I was putting binary values of
various types into a buffer. When I tried to pass that buffer to printf()
using the proper formatting string for the number's type and a cast on the
buffer to get the right length (but not "(float)") all the printf() formats
worked except for "%f", the output of which was "-nan", ie Not A Number.

I then started asking myself why this did not work. After all a float is
the same size as an int so why should there be a problem of "%d" vs. "%f"
printf() formats? Well, I found the aforementioned link which told me this
is probably some weirdness in GCC "C" specific to float function arguments.
Aside from the asymmetry of passing various 32-bit types differently I ran
into something else when I started playing around with it, that being that
a function call using floats which modifies state visible to the caller.

The program below shows there is no problem when passing ints to printf()
but the same code using floats alters something -- has a side-effect which
is visible to the caller.

WHY is a 32-bit float passed to a function in a manner different from a
32-bit int???

I also think that it is a really bad idea to have undocumented side effects
like that. Or did I already say that?

========================================================================

Here is some version information:

> uname -a
Linux atomik 2.6.37.6-smp #1 SMP Sat Apr 9 14:01:14 CDT 2011 i686 Intel(R) \
Atom(TM) CPU D510 @ 1.66GHz GenuineIntel GNU/Linux

> cat /etc/slackware-version
Slackware 13.37.0

> gcc --version
gcc (GCC) 4.5.2

> ls -1 /var/log/packages/glib*
/var/log/packages/glib-1.2.10-i486-3
/var/log/packages/glib2-2.28.6-i486-1
/var/log/packages/glibc-2.13-i486-4
/var/log/packages/glibc-i18n-2.13-i486-4
/var/log/packages/glibc-profile-2.13-i486-4
/var/log/packages/glibc-solibs-2.13-i486-4
/var/log/packages/glibc-zoneinfo-2.13-noarch-4
/var/log/packages/glibc-zoneinfo-2011i_2011n-noarch-1


And here is the program followed by its output. Watch (int)floatVarA change
after successive calls to printf() just passing a float.

========================================================================

// Program to show UNDOCUMENTED side-effects IN THE CALLER'S SPACE when passing
// float variables to subroutines which should change NOTHING in the caller's
// space. See http://en.wikipedia.org/wiki/X86_cal...ventions#cdecl where
// it says, "In Linux/GCC, double/floating point values should be pushed on the
// stack via the x87 pseudo-stack." I'm not sure but that may be related.

#include <stdio.h>
#include <stdlib.h>

int
main ( int argc, char *argv[])
{
int intVarA = 37;
int intVarB = 42;

float floatVarB = 42.0;
float floatVarA = 37.0;

// Show where things are and their sizes.

printf("\nintVarA is at %p of size %d\n", &intVarA, sizeof( intVarA));
printf("intVarB is at %p of size %d\n", &intVarB, sizeof( intVarB));

printf("\nfloatVarA is at %p of size %d\n", &floatVarA, sizeof( floatVarA));
printf("floatVarB is at %p of size %d\n", &floatVarB, sizeof( floatVarB));

// Do all the assignments.

intVarA = 1234;
intVarB = 5678;

floatVarA = 1234.0;
floatVarB = 5678.0;

// From here on out nothing in main's space should change and for
// calls with int types it does not ...

printf( "\n(int)intVarA = %d\n", (int)intVarA); // Prints 1234
printf( "intVarA = %d\n", intVarA);
printf( "(int)intVarA = %d\n", (int)intVarA); // Prints 1234
printf( "intVarB = %d\n", intVarB);
printf( "(int)intVarA = %d\n", (int)intVarA); // Prints 1234
printf( "intVarA = %d\n", intVarA);

// ... but something IS changing in MAIN's space with float variables. BAD!

printf( "\n(int)floatVarA = %f\n", (int)floatVarA); // Prints 0.0
printf( "floatVarA = %f\n", floatVarA);
printf( "(int)floatVarA = %f\n", (int)floatVarA); // Prints 1234.0
printf( "floatVarB = %f\n", floatVarB);
printf( "(int)floatVarA = %f\n", (int)floatVarA); // Prints 5678.0
printf( "floatVarA = %f\n", floatVarA);

return 0;
}

========================================================================

> cc float_passing_insanity.c && ./a.out

intVarA is at 0xbf80155c of size 4
intVarB is at 0xbf801558 of size 4

floatVarA is at 0xbf801550 of size 4
floatVarB is at 0xbf801554 of size 4

(int)intVarA = 1234
intVarA = 1234
(int)intVarA = 1234
intVarB = 5678
(int)intVarA = 1234
intVarA = 1234

(int)floatVarA = 0.000000
floatVarA = 1234.000000
(int)floatVarA = 1234.000000
floatVarB = 5678.000000
(int)floatVarA = 5678.000000
floatVarA = 1234.000000

========================================================================

johnsfine 08-20-2012 04:35 PM

Edit: After reading your question more carefully, I see I focused too much on the question you asked rather than your original problem that launched you into this misguided experiment.

"%f" in printf is for doubles, not floats. When you pass a float, printf receives a double (see more info below).

I don't know exactly what the original code (that you didn't post) did wrong, but the symptoms imply you managed to suppress the actual conversion of float to double and instead reinterpret a float and the following unrelated four bytes as a double.

Quote:

Originally Posted by GreyBeard (Post 4759552)
I've run into a problem which appears to be doing an undocumented change
of state in the caller's space when passing float variables to a function,

I understand exactly why you get exactly that effect. But it is only effecting the symptoms of a bug in your code. The symptoms of incorrect code are not supposed to be stable or predictable.

Quote:

Undocumented
side effects in the caller's space strike me as being very bad.
Under these circumstances, I disagree. There is nothing wrong with side effects to an area of memory whose contents only influences the behavior of incorrect subsequent code. The behavior of correct subsequent code would not be affected.

Specifically, your code is depending on the contents of an undefined portion of the stack by passing data that is too small into printf (in 32 bit x86). In other architectures, this same passing of wrong info to printf would have different symptoms.

Quote:

It is the passing of a float variable which
is at fault.
Nonsense. Passing a float is fine. Calling a function typically modified the contents of the undefined portion of the stack (as viewed after the function completes). In this case the value of the float is left in an undefined portion of the stack.

Quote:

There is some weirdness
going on which I do not understand concerning how floats are passed.
Nothing weird here at all (you should see how floats are passed in win64). In x86 32 bit, parameters are simply passed on the stack.

Quote:

I tried to pass that buffer to printf()
using the proper formatting string for the number's type
How is the format string proper when you have cast the number to a different type.

Quote:

and a cast on the
buffer to get the right length
Maybe that is the key to your confusion. You are casting the value to a different type. How do you see that as casting a buffer to a different length?

Quote:

After all a float is
the same size as an int
Look up "default argument promotion", which applies because this is part of a variable length argument list. The float is passed as a double.

You should know that "%f" is the format for double, not for float.

Quote:

WHY is a 32-bit float passed to a function in a manner different from a
32-bit int???
That is generally true and the details vary a lot by architecture and goes beyond the fact that floats may be promoted to doubles. So there may be more to making your code usable (especially in any portable way) than simply realizing that %f is for doubles.


All times are GMT -5. The time now is 02:32 AM.