Reputation: 3654
Basically, is it safe to do the following:
We have a simple function call accepting an arbitrary number of arguments:
void simpleCall(int x, int y, int z, int a, int l, int p, int k)
{
printf("%i %i %i %i %i %i %i\n", x, y, z, a, l, p, k);
}
We create a struct that maps to the arguments:
struct simpleArgs
{
int x;
int y;
int z;
int a;
int l;
int p;
int k;
};
We initialize the structure with the arguments we want to pass, cast the function so it will compile, and pass the structure in place of all the arguments:
int main()
{
simpleArgs a;
a.x = 1;
a.y = 2;
a.z = 3;
a.a = 4;
a.l = 5;
a.p = 6;
a.k = 7;
((void(*)(simpleArgs))simpleCall)(a);
return 0;
}
Program prints:
1 2 3 4 5 6 7
This particular example serves no purpose (we could just as easily call the function normally) however it's written to illustrate the concept. Because the struct is passed by value, does this compile down identically to passing each argument by value? A standard call places the arguments on the stack as a group which should be the same as what we're seeing here? Under what conditions are arguments passed via registers?
It looks like the fact that this is working might be a fluke of cdecl x86, the compilers I tested on, and the argument types I've selected. This appears to be so fragile that if you pick some variable types that are not 16-bit aligned and without explicitly modifying the struct to align it, expect a SIGSEV.
Upvotes: 2
Views: 113
Reputation: 129524
Passing individual arguments and passing one argument as a struct
is very often dealt with as different things. There is, for example, nothing saying that struct
is just passed like it is:
struct X x;
... fill in x...
func(x);
Rather, sometimes it becomes:
struct X x;
... fill in x...
struct X tmp = x;
func(&tmp);
(in other words, the copy of the argument is made at the call-site, and then passed as an address, not inside the function or during the actual call - coincidentally, this is how my Pascal compiler performs such passing).
Sometimes the size of the struct will determine whether the whole structure is loaded into registers or passed as a copy in memory (where the individual arguments would be a mixture of registers and memory). The order of arguments may differ from the order the struct is stored in memory.
There is absolutely not one single thing that says this should work. It may do, by chance. It may also break if you decided to alter the compiler optimisation level or compile for a different processor type.
Upvotes: 3
Reputation: 29
It is definitely not portable, and even if it works now this is the sort of thing that might come back later if you were to port the code to a different platform.
The much better solution would be to simply overload the function like this:
inline void simpleCall(simpleArgs args){
simpleCall(args.x,args.y,args.z,args.a,args.l,args.p,args.k);
}
int main(){
simpleArgs a = {1,2,3,4,5,6,7};
simpleCall(a);
return 0;
}
Due to the simplicity of the function and the inline
keyword it be optimized away, so the actual code being executed would be as if you had just called the function directly.
Upvotes: 1