【问题标题】:Cast va_list to pointer, then increment it将 va_list 转换为指针,然后将其递增
【发布时间】:2013-09-18 13:45:16
【问题描述】:

我正在使用的编译器(ARM 的 Codesourcery)中有一个错误会破坏 va_arg(),我正在尝试解决这个问题。在这种情况下,'ap' 是一个指向 32 位和 64 位参数列表的简单指针。编译器 bug 是 va_arg() 坏了,有时会返回不正确的值。

我可以将 va_list 转换为任意类型的指针,并使用它来提取列表中的值:

void foo(va_list ap)
{
    int32_t  ival;
    double   dval;

    ival = *(int32_t*)≈
    dval = *(double*)&ap);
}

但是,如何将增量“ap”作为强制转换类型预先或后置?

例如,这两个都给出错误:

(int32_t*)&ap++;
++(int32_t*)&ap.

真正的“C”大师能帮我一把吗?我有一个使用联合来操作指针的解决方案,但我想要一个更“c-worthy”的方法......

【问题讨论】:

  • 您使用的是哪个版本的编译器?你如何调用它?什么优化标志等等...
  • 我希望你的嵌入式产品是一个不重要的小工具(而不是真正有用的东西,比如空调系统),我不会购买或使用它。看到(恕我直言,非常不专业)的行为让我感到害怕“我不介意未定义的行为;它对我有用一次,所以对其他人应该没问题......”
  • 其实不会是未定义的。该修复是在编组 va_list 上的参数和从 va_list 中提取值时分析编译器生成的汇编代码的结果。 va_arg() 宏生成的代码错误地计算了地址。因此,如果您知道参数是如何编组的,那么您就知道应该如何检索它们。除非我们更改编译器(并且如果编译器的行为不同),否则不会有问题。 QED。
  • 但是几个月后,随着产品的更新,您稍微更改您的代码和编译器......所以我很害怕(更不用说我我对其余代码的质量没有信心)。
  • 我不建议其他人采用此修复程序,顺便说一句。这似乎是一个影响某些 ARM 实现的相对不寻常的问题。而且,我不是真正的程序员,但我在电视上玩过一个......

标签: pointers casting variadic-functions


【解决方案1】:

事实证明,这根本不是编译器问题。该问题是由堆栈指针不在 8 字节边界上引起的。通过修改加载脚本以对堆栈使用 8 字节对齐来修复它。我应该补充一点,这个项目正在使用 NutOS;一段非常有用的代码。

使用股票加载脚本,堆栈指针被加载(有时,并非总是)一个不是 8 字节对齐的值。

我的 ARM9 Linux 平台没有这个问题,尽管参数编组和 va_arg() 代码与 ARM7 编译器相同。

我注意到,当调用函数时,编译器会使用它认为会导致 8 字节对齐的值加载堆栈指针。这导致 r0-r3 是 8 字节对齐的。使用 8 字节对齐,va_arg() 算术有效。

我知道这不能回答我的问题。我确实制定了一个涉及联合的解决方案,有趣的是,你认为额外的代码最终被优化了。因此,使用联合来操作 va_list 并没有真正增加太多开销。

感谢大家的回复。

【讨论】:

    【解决方案2】:

    不保证va_list 可以转换为指针(它可能通过涉及多个寄存器或字的编译器魔法来实现)。

    您应该只使用stdarg(3) 中记录的宏,即va_startva_endva_arg(可能还有va_copy)。

    根据定义,您的代码是不可移植且有缺陷的,它会触发undefined behavior

    要增加ap,请仅使用va_arg(ap, int32_t) 等...;

    顺便说一句,您可以下载GCC 的新版本(即今天的 4.8.1,2013 年 9 月 18 日)并从源代码编译它(也许作为交叉编译器)。它可能已经解决了您遇到的编译器错误。

    【讨论】:

    • va_start()、va_copy() 和 va_end() 都可以工作。问题是 va_arg() 已损坏,有时会给出不正确的结果。将“ap”转换为某种类型以获取值有效;我的问题是如何编写递增或递减操作。
    • 是的,代码是不可移植的。没关系,它是使用 AT91SAM7X 处理器的嵌入式设备。不过,你只是给了我一个想法,谢谢。我会尝试使用 va_arg() 来增加......
    • 是什么让您认为嵌入式处理器可以使用未定义的行为错误(可能会通过稍微不同的编译器或优化选项再次触发......)? !!
    • 没关系,因为这是专用应用程序;编译器不会改变,我们可以验证我们使用的东西是否可以正常工作。顺便说一句,不能使用 va_arg() 来增加,因为它得到优化。
    【解决方案3】:

    强制转换和 & 运算符都不会返回可分配的值(也称为“L 值”,可以位于等号的左侧)。你可以试试:

    int ival;
    void * pap = ≈
    ival = *(int32_t*)pap;
    pap += sizeof(int32_t);
    

    但是,正如前面的回答所说,va_list 实现会因平台而异,任何解决方法都不太可能是可移植的。如果您决定继续使用解决方法,至少要确保包含 va_list 的单元测试,以便您知道它是否会中断。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-13
      • 2012-10-24
      • 2015-09-17
      相关资源
      最近更新 更多