编译器之间匹配 va_list 类型

发布于 2024-09-25 03:21:17 字数 2261 浏览 7 评论 0原文

我有一个由一堆动态加载的模块组成的项目。最初,一切都是用 MSVC 2003 构建的,但最近我一直致力于让它与 GCC 一起工作。一切都进行得很顺利,除了一个问题。对于 64 位代码,GCC 和 MSVC 对于 va_list 的含义并不一致。对于 32 位,一切似乎都很好。 64 位不匹配导致的问题是,当使用一个编译器构建的模块具有带有 va_list 参数的公共函数,并且从另一个编译器构建的模块调用该函数时。

除了第 7.15 节变量参数第 3 段之外,该规范没有提及 va_list 是什么:

声明的类型是

va_list

这是一种适合保存宏所需信息的对象类型va_startva_argva_endva_copy

该段落只是意味着这都是编译器相关的内容 - 那么,有没有办法让这两个编译器就 64 位 va_list 的内容达成一致?为了尽量减少对我的系统的影响,最好让 GCC 与 MSVC va_list 匹配,但我会采取任何我能得到的解决方案。

感谢您的帮助!

编辑:

我做了一些 32 位测试,我也遇到了问题,这让我感到惊讶,因为据说任何 32 位 Intel 平台之间都没有 ABI 差异。我使用的 MSVC 代码库将所有可变函数宏定义为:

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)

我对实际项目进行了一些简化,但这是我用于测试的代码。对于 GCC,这段代码绝对不能正确地得到我的参数。也许这只是一个错误,正如扎克在下面建议的那样?

再次编辑:

我使用 -O0-O0-O2 获得了以下 32 位测试应用程序的工作结果,但没有 <代码>-O3、<代码>-Os和<代码>-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;
}

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, and va_copy.

That paragraph just means this is all compiler dependent stuff - so, is there a way to make 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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

秋心╮凉 2024-10-02 03:21:17

由于 MSVC 定义了 Win64 ABI,因此您在 GCC 中发现了一个错误。请在 GCC bugzilla 中报告该问题。

Since MSVC defines the Win64 ABI, you have found a bug in GCC. Please report it in GCC bugzilla.

白馒头 2024-10-02 03:21:17

由于 va_list 似乎没有 ABI(或者至少 MSVC 和 GCC 不同意 ABI),因此您可能需要自己整理这些参数。

我能想到的解决这个问题的最直接的方法是将变量参数编组到动态分配的内存块中,并传递指向该块的指针。

当然,这样做的缺点是完全改变了当前使用 va_args 的函数的接口。

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.

挖鼻大婶 2024-10-02 03:21:17

因为没有关于如何处理 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 :)

不喜欢何必死缠烂打 2024-10-02 03:21:17

尝试通过汇编级调试器(例如 ollydbg)运行它,因为您的问题可能不在于 va_args,而在于编译器期望传递参数的方式(gcc 可能期望它们采用 linux 格式,而 msvc正在使用 win64 __fastcall),如果有的话,这将使情况更加清晰。另一种相当 hacky 的方法是尝试摆弄用于 64 位参数的定义,例如将 msvc 宏导入到 gcc 头中(当然使用项目本地副本),看看是否可以解决任何问题。

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.

涫野音 2024-10-02 03:21:17

您使用什么类型?严格来说,WinXX 仅定义可变参数的字符、字符串、指针和整数的行为 (请参阅 user32.dll 中 wsprintf 的文档)。因此,如果传递浮点值或结构,从技术上讲,结果对于 Windows 平台是未指定的。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文