【问题标题】:Run-time parameters in gcc (inverse va_args/varargs)gcc 中的运行时参数(反向 va_args/varargs)
【发布时间】:2014-04-05 20:46:31
【问题描述】:

我正在尝试对我正在开发的微控制器的解释器进行一些改进。为了执行内置函数,我目前有这样的东西(虽然快一点):

function executeBuiltin(functionName, functionArgs) {
  if (functionName=="foo") foo(getIntFromArg(functionArgs[0]));
  if (functionName=="bar") bar(getIntFromArg(functionArgs[0]),getBoolFromArg(functionArgs[1]),getFloatFromArg(functionArgs[2]));
  if (functionName=="baz") baz();
  ...
}

但它适用于资源非常有限的嵌入式设备 (ARM),我需要大幅缩减代码大小。我想做的是有一个通用函数来调用具有不同参数的其他函数 - 像这样:

function executeBuiltin(functionName, functionArgs) {
  functionData = fast_lookup(functionName);
  call_with_args(functionData.functionPointer, functionData.functionArgumentTypes, functionArgs);
}

所以我希望能够调用一个标准的 C 函数并将它需要的任何参数传递给它(它们都可以是不同的类型)。为此,我需要一个call_with_args 函数。

我想避免重写每个函数来获取 argc+argv。理想情况下,被调用的每个函数都是完全标准的 C 函数。

有一个discussion about this here - 但自 1993 年写该帖子以来有什么变化吗?特别是当我在 ARM 上运行时,参数位于寄存器中而不是堆栈中。即使它不在标准 C 中,有什么 GCC 特定的可以做的吗?


更新: 似乎尽管行为根据规范是“未定义的”,但由于 C 调用的工作方式,您可以向函数传递比预期更多的参数,并且一切都会好起来的,因此您可以将所有参数解压缩到一个 uint32 数组中,然后只需将每个 uint32 传递给函数。

这使得为调用编写“漂亮”代码变得更加容易,而且看起来运行良好(在 32 位平台上)。唯一的问题似乎是在传递 64 位数字并为 64 位 x86 编译时,因为在这种情况下它似乎做了一些特别奇怪的事情。

【问题讨论】:

  • libffi 可能会有所帮助。如果你必须减少内存占用,它可能太大而无法包含,但源代码应该会给你一些指示。
  • 是的!我发现与您的更新相同,使用 vararg 函数指针。事实上,在link I have already referred on arm arch 中,它声明“始终按照基本标准编组可变参数函数”。但在发布之前,我想进一步调查一下。你已经得到它在我面前 - 好! xf86_64 又是另一个野兽,因为它使用(在 linux 上)cdecl 约定——一些操作数(肯定是 FP 值)在寄存器上传递。见这里:en.wikipedia.org/wiki/X86_calling_conventions#cdecl.
  • 谢谢!所以这有点像噩梦——如果浮点和整数的处理方式不同,那就特别痛苦。我想我可能必须为 x86(代码大小无关紧要)创建一个基于签名的调用程序,然后我可以在 ARM 上使用更简单的东西。

标签: c function gcc argument-passing variadic-functions


【解决方案1】:

你可以做一个非常简单的序列化来传递任意参数。为每个传递的参数创建一个数组和 memcpy sizeof(arg) 字节。

或者您可以为函数参数创建结构。

每个函数都采用 char* 或 void*。然后,您可以将指针传递给具有该函数参数的结构,或者定义一组宏或函数来编码和解码数组中的任意数据并将指针传递给该数组。

【讨论】:

  • 我不确定这是否解决了调用现有库函数的问题,例如sin?
  • @GordonWilliams 你也不能调用那些使用可变参数的人。所以你需要一个代理函数来解码它,然后调用 sin。
【解决方案2】:

是否可以在编译时使用宏? 类似于以下内容: https://www.redhat.com/archives/libvir-list/2014-March/msg00730.html

如果需要运行时,也许可以利用 __buildin_apply_args()。

【讨论】:

  • __builtin_apply 看起来真的很有趣。我还没有找到一个完全符合我要求的示例,但它似乎很有希望 - 我必须进行实验。
【解决方案3】:

来自this document,第 5.5 节,参数传递,参数似乎在寄存器和堆栈中都传递,就像当今大多数平台一样。

对于“非标准 C”,我正在考虑使用一些 asm() 打包参数并按照文档调用函数。但是,无论如何,您都需要有关被调用函数签名的最少信息(我的意思是,每个参数要传递多少位)。

从这个角度来看,我更愿意准备一个函数名数组、一个函数指针数组和一个枚举函数签名数组(以每个参数的位数......你不需要区分例如,来自 char* 的 void*)和签名上的 switch/case,以及最后一个上的 switch/case。所以我在这里报告了两个答案。

【讨论】:

  • 我有大约 400 个函数,我希望它会增长。我将不得不看看有多少独特的签名,但我的感觉是它可能很多,这意味着切换签名将远非理想。至于用汇编程序推送正确的参数 - 是的,可以做到,但问题是真的有 va_args 等价物,或者从 1993 年起是否没有任何变化(似乎没有)。看起来 nwellnhof 对 libffi 的建议是目前最接近简洁的解决方案。
  • 补充一下,我检查了,如果我将每个参数分类为 32 个 64 位,则有 400 个具有 25 个唯一签名的函数,如果不分类,则有超过 80 个。 IMO 最好采用更灵活的方法就足够了。
猜你喜欢
  • 2023-03-25
  • 2013-07-16
  • 1970-01-01
  • 2020-12-09
  • 1970-01-01
  • 2011-01-12
  • 2011-03-09
  • 1970-01-01
  • 2018-02-18
相关资源
最近更新 更多