1

In my main function, I use the following code

float f = 32.0;
func("test string %f", f);

func (these are all example names) is declared as following

void func(const char *str, ...);

In my implementation of this function, I use a union called all_types to obtain the value of the arguments that are passed

union all_types
{
    void *v;
    CLObject *obj;
    char *s;
    long l;
    char c;
    float f;
    int i;
    double d;
};

and then give a value to that union like this

union all_types *o = calloc(1, sizeof(union all_types));
while ((o->v = va_arg(list, void *)) != NULL)

Now, when I know the argument is a float, the value for it will be very strange (I set a breakpoint to figure it out). The i and l values on the union will be 32, as they should. However, the f value is some weird number like 0.00000000000000000000000000000000000000000013592595. Does anyone know why I am getting this behavior? This function works for every other type of object I have tested.

timrau
  • 22,578
  • 4
  • 51
  • 64
Chris Loonam
  • 5,735
  • 6
  • 41
  • 63

2 Answers2

3

The va_arg macro's second argument is the actual type of the actual argument. No conversion takes place as a result of the va_arg invocation. If you don't know the actual type of the actual argument, you're out of luck because there is no way to find out.

Note that default argument conversions do take place in the call itself, so it is impossible to receive a float, char or unsigned short. (The float will be converted to double and the other two to int or unsigned int, depending.)

This is why printf formats make you specify the type of the argument, except for float.

rici
  • 234,347
  • 28
  • 237
  • 341
  • What I am basically doing in this function is trying to implement a sort of printf, and as with printf, I use format specifiers. By using a union combined with the specifiers, I have been able to get this to work with every other type. `float` is the only one I've had a problem with. – Chris Loonam Feb 14 '14 at 04:07
  • @ChrisLoonam: You might get lucky. Don't get used to it. It won't work on someone else's system, with someone else's compiler, or with the next version of your compiler. – rici Feb 14 '14 at 04:08
  • @ChrisLoonam, so what you want is something like http://stackoverflow.com/questions/5977326/call-printf-using-va-list – Richard Chambers Feb 14 '14 at 04:10
  • @RichardChambers I would, but I was doing this to add support for a custom type in printf. – Chris Loonam Feb 14 '14 at 04:12
  • 1
    @ChrisLoonam, what I have seen done for special types with printf() is to write your own version of printf(). Usually it is done by writing a special version of an in memory sprintf() type of clone since that provides quite a bit of flexibility. To do it right you really should support variable args as in http://stackoverflow.com/questions/1485805/whats-the-difference-between-the-printf-and-vprintf-function-families-and-when – Richard Chambers Feb 14 '14 at 04:19
  • @ChrisLoonam, take a look at this example for a starting place http://www.opensource.apple.com/source/ruby/ruby-67.6/ruby/sprintf.c – Richard Chambers Feb 14 '14 at 04:23
3

What you are doing invokes undefined behavior, variadic functions will convert floats to double and the undefined behavior comes in because void * is not compatible with double and so you can have no expectation as to the result. We can see this by going to the draft C99 standard section 7.15.1.1 The va_arg macro which says:

[...]If there is no actual next argument, or if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined,[...]

The correct way to do this would be:

o->d = va_arg(list, double)

and you have the format specifier so this should be possible:

"test string %f"
             ^^
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740