Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to retrieve a float from a varargs function's parameters?

If the function was defined with a prototype which explicitly stated the types of the parameters, eg.

void somefunc(int arg1, float arg2);

but is implemented as

void somefunc(int arg1, ...) { ... }

is it possible to use va_arg to retrieve a float? It's normally prevented from doing this because varargs functions have implicit type promotions, like float to double, so trying to retrieve an unpromoted type is unsupported, even though the function is being called with the unpromoted type do to the more specific function prototype.

The reason for this is to retrieve arguments of different types at runtime, as part of an obj-c interpreter, where one function will be reused for all different types of methods.

This would be best as architecture independent (so that if nothing else the same code works on simulator and on device), although if there is no way to do this then device specific fixes will be accepted.

EDIT: forgot to mention specifically: the function knows the types and count of the arguments (it looks up the code to be interpreted with a map lookup with the SEL _cmd parameter)

like image 229
Jared Pochtar Avatar asked Sep 15 '25 05:09

Jared Pochtar


2 Answers

You are pretty much going to have to use per-architecture assembly to do this. First, you can't use varargs because -- as you implied -- the calling ABI for varargs is different than the calling ABI for non-varargs; arguments are encoded differently and the register state is different across the call boundary.

The easiest way to do this is to create a ton of stub functions w/all the variations of argumentation you'll ever need. Each stub then takes the specific arguments and bundles 'em up into something more general for your code's more general consumption.

If you don't want to go that route, then you are going to have to know the types of the arguments, the encoding rules for arguments for the particular target ABI, and then you'll need to write the code to effectively rip the arguments out of their hidy-holes when your generic trampoline is called.

And you'll need to do all that while also not destroying any of the arguments through inadvertent register use. You might find my write-up of objc_msgSend() to be indirectly useful in that it describes exactly how Objective-C handles this issue (hint: it goes to great lengths to not touch any of the arguments beyond the first two).

like image 102
bbum Avatar answered Sep 17 '25 00:09

bbum


You could do the following:

static inline uint32_t floatparam (float f) {
  union { uint32_t u; float f; } u;
  u.f = f;
  return u.u;
}

then always call your function like so:

fn(5, floatparam(0.5f), floatparam(1.1f), ...);

In your function, you'd then do

va_list val;

va_start (val, arg1);
while (<more arguments>) {
  union { float f; uint32_t u; } u;
  u.u = va_arg (val, uint32_t);
  // float is now in u.f
}

That avoids the problem of needless type promotions, and doesn't require assembly language.

Of course, you shouldn't mis-declare your function. If it's a varargs function, it's a varargs function, period. As bbum says, the ABI is different.

like image 32
al45tair Avatar answered Sep 16 '25 22:09

al45tair