Matching va_list types between compilers
I have a project that consists of a bunch of dynamically loaded modules. Originally, everything was always built with MSVC 2003, but lately I've been working on getting it to work with GCC. Everything has been going pretty smoothly, except for one problem. For 64-bit code, GCC and MSVC don't agree about what a va_list
is. For 32-bit, things seem to line up fine. The problem the 64-bit mismatch causes is when a module built with one compiler has a public function with a va_list
parameter and that function is called from a module built by the other compiler.
The spec says nothing about what a va_list
is, outside of Section 7.15 Variable arguments <stdarg.h>
, paragraph 3:
The type declared is
va_list
which is an object type suitable for holding information needed by the macros
va_start
,va_arg
,va_end
, andva_copy
.
That paragraph just means this is all compiler dependent stuff - so, is there a way to m开发者_如何学Goake these two compilers agree on the contents of a 64-bit va_list
? For least impact to my system, making GCC match the MSVC va_list
would be best, but I'll take any solution I can get.
Thanks for helping out!
Edit:
I did some 32-bit testing, and I have problems there too, which surprised me since there are supposedly no ABI differences between any 32-bit Intel platforms. The MSVC codebase I'm using defines all of the variadic function macros as:
typedef char *va_list;
#define intsizeof(n) ((sizeof(n) + sizeof(int) - 1) &~(sizeof(int) - 1))
#define va_start(ap, v) (ap = (va_list)&(v) + intsizeof(v))
#define va_arg(ap, t) (*(t *) ((ap += intsizeof(t)) - intsizeof(t)))
#define va_end(ap) (ap = (va_list)0)
I've simplified a bit from the real project, but this is the code I was using for my test. With GCC, this code definitely isn't correctly getting my arguments. Maybe it is just a bug, as Zack suggests below?
Edit again:
I get working results for the following 32-bit test application with -O0
, -O0
, and -O2
, but not -O3
, -Os
, and -Oz
:
typedef char *va_list;
#define intsizeof(n) ((sizeof(n) + sizeof(int) - 1) &~(sizeof(int) - 1))
#define va_start(ap, v) (ap = (va_list)&(v) + intsizeof(v))
#define va_arg(ap, t) (*(t *) ((ap += intsizeof(t)) - intsizeof(t)))
#define va_end(ap) (ap = (va_list)0)
int printf(const char *format, ...);
int f(int n, ...)
{
int r = 0;
va_list ap;
va_start(ap, n);
while (n--)
r = va_arg(ap, int);
va_end(ap);
return r;
}
int main(int argc, char **argv)
{
int r;
r = f(1, 1, 2, 3, 4, 5);
printf("%x\n", r);
r = f(2, 1, 2, 3, 4, 5);
printf("%x\n", r);
r = f(3, 1, 2, 3, 4, 5);
printf("%x\n", r);
r = f(4, 1, 2, 3, 4, 5);
printf("%x\n", r);
r = f(5, 1, 2, 3, 4, 5);
printf("%x\n", r);
return 0;
}
Since MSVC defines the Win64 ABI, you have found a bug in GCC. Please report it in GCC bugzilla.
Since there doesn't seem to be an ABI for va_list
(or at least MSVC and GCC don't agree on an ABI), you'll probably need to marshal those parameters yourself.
The most straightforward way I can think of to work around this problem off the top of my head is to marshal your variable parameters in a dynamically allocated block of memory and pass a pointer to that block.
Of course, this has the drawback of completely changing the interface to the functions that are currently using va_args.
Because there is no standard on how va_args have to be handlded, if you need this functionality to be consistant in a cross compiled platform, you'd probably be better off rolling your own version. We didn't do this and have been burned multiple times recently as a result when supporting additional targets for our codebase. I'd love to be wrong though if others have a better solution :)
Try running it through an assembly level debugger such as ollydbg, as your problem might not be with the va_args, but rather with the way the compiler is expecting the args to be passed(gcc could be expecting them in the linux format, where as msvc is using win64 __fastcall), if anything this will give a little more light to the situation. Another, rather hacky approach, is try fiddling with the definitions used for the 64 bit args, such as importing the msvc macros into the gcc header(use a project local copy of course), see if that remedies anything.
What types are you using? Strictly speaking, WinXX only defines behavior for characters, strings, pointers, and integers for varargs (see documentation for wsprintf in user32.dll). So, if you pass down floating point values or structures, the results are technically unspecified for the Windows platform.
精彩评论