问题 在调用va_end之前,longjmp可以吗?


在此问答中,确定您应该始终致电 va_end()

va_end究竟是什么?是否总是需要打电话给它?

但是如果你到达va_end之前有一段代码longjmp呢? va_end的部分是否有任何承诺可以接受?或者在概念上可能是它 (例如) 做一个内存分配 va_start() 那会被泄露,而不仅仅是使用堆栈技巧?


11279
2017-08-27 21:13


起源

我想你需要打电话 va_end 在打电话之前 longjmp,你应该这样做 free() 任何商业活动都被跳过了 - M.M
这不是问题的关键吗? - Weather Vane
@WeatherVane是的。问题是如果你正在做什么来处理每个参数项可以longjmp。如果是这样,那么你必须把变量args放在另一个地方然后再打电话......但是到底是什么地方?可能是一个有限大小的有限堆栈数组。如果va_args已经可以作为那个地方,那将会很好......而在实际实现中,它通常是。但该标准不太可能承诺。 - HostileFork
@supercat我在嵌入式系统中使用了一种残酷的方法作为最后的手段,当所有其他方法都失败但是节目必须继续时,它会让你提到的部分重启,没有什么是悬空的。 - Weather Vane
在有或没有多任务的嵌入式系统中@supercat我发现最好的方法是由计时器驱动的中间“心跳”层,它将从中断服务信息并将其呈现给“主循环”,尽管这是简化的声明。处理错误的最佳方法,将它们从中断作为状态值传递,或从子程序作为返回值传递。我认为跳远比软件相当于短路。如果一切都失败了,相当于重置按钮。如果你 设计 长期跳进基于堆栈的系统,因为你可以,羞辱你。 - Weather Vane


答案:


C99理由 明确说明 va_start 可以分配最终释放的内存 va_end,正是你在问题中猜到的:

7.15.1.2 va_copy 宏

[...]

30一个更简单的方法是复制 va_list 用于表示参数处理的对象。但是,没有安全的方法   在C89中执行此操作,因为该对象可能包含指向由内存分配的内存的指针 va_start 宏观和被摧毁 va_end 宏。
  新的 va_copy 宏提供了这种安全机制。

[...]

所以是的,你需要调用 va_end 在...之前 longjmp。至少你会在这样的实现上发生内存泄漏。


据说金字塔OSx有一个实现内存分配的实现 va_start。函数参数在寄存器中传递。即使对于可变函数也是如此。它可能早于ANSI C发明的函数原型,这意味着调用者不知道它是否处理可变函数。 va_start 分配内存,大概是以某种方式存储函数参数值 va_arg 可以轻松访问它。 va_end 释放分配的内存。

它的实施 va_start 和 va_end 实际上需要匹配 va_start 和 va_end 从语法上讲,因为它是一个使用不平衡的括号,所以ANSI C已经不允许这种实现,但是同样的原则可以在匹配大括号时工作。

我可以找到关于这个实现的非常少的具体信息,它在80年代末,90年代早期的Usenet上只是点点滴滴。我找到的一点点可能是不完整的,甚至是完全错误的。更多细节非常受欢迎,尤其是那些自己使用此实现的人。


8
2017-08-27 21:25



至少有些人在这里是明智的。投票。 - Weather Vane
我认为这也值得引用 va_end 包含的理由 In many implementations, this is a do-nothing operation; but those implementations that need it probably need it badly.。 badly 虽然很模糊。 - cremno
“那些需要它的实现可能需要它很糟糕” ......嘿,非常好笑。 :-) - HostileFork
@cremno我试图通过找到一个具体的实现示例来解决这个问题 va_end 不是无操作,但我很怀疑我会找到任何。
只是添加当前标准: 7.16.1.3p2 和 7.16.1.2。但是,它只会引用“返回”,而不是longjmp而不是分配的内存。我不太清楚: stdarg.h 不 不 要求 stdlib.h 或任何其他内存分配功能。那么它只是指堆栈分配/自动内存吗?如果是这样,那么longjmp不会对此进行妥善处理吗? - too honest for this site


如果你正在使用 jmp_buff 存储在全局变量(通常的模式)中,制作它的副本并使用它应该是安全的 setjmp 所以 longjmp 将转到您的代码而不是外部调用者;的情况下 longjmp,然后你的代码可以调用 va_end 和 longjmp 使用存储的缓冲区副本;如果您的代码正常退出,则需要在返回之前恢复全局缓冲区。


2
2017-08-27 22:24



实际上@HostileFork,如果你的代码不会在非平凡的平台上运行 va_end 那我可能只是忽略了这个问题。但是,大概你必须有适当的东西来处理跳转代码中发生的堆分配。 - M.M