views:

280

answers:

4

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)

+5  A: 

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).

bbum
A: 

You can always ditch varargs and instead encode all of your parameters into a string. Your function interface remains constant, and you have complete freedom to interpret the data however you need to. It's not a pretty solution, however, since you're essentially implementing a script parser.

bta
+2  A: 

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.

alastair
do you happen to know if this works on device?
Jared P
It should work anywhere where float is 32-bits long; you might need "stdint.h" or "inttypes.h", unless you replace the uint32_t type with something else, but most compilers have those these days.
alastair
I don't think, FWIW, that this is going to be a good way to implement an Objective-C runtime. Better to do it the way Apple does, although that requires assembly language.
alastair
It depends entirely on need, which OP hasn't provided. If the goal is for fn() to be plugged into a class as an IMP, it ain't gonna work generically.
bbum
+1  A: 

If the function is declared and defined as you show, there's no way to do anything, especially in architecture-independent way. The code is broken. It doesn't work. There's absolutely no compatibility between variadic and non-variadic functions in C. These are functions of completely different nature, they belong to two completely different non-intersecting worlds.

If in your specific implementation/architecture these two can be somehow forced to work together, this is a specific detail of your implementation. In that case you have to research your specific architecture.

AndreyT