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

Jeff B a.GNUbie@myLetters.US
Mon Aug 20 23:26:00 GMT 2012


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_calling_conventions#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_calling_conventions#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

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



More information about the Gcc-help mailing list