【问题标题】:Is it okay to longjmp before calling va_end?在调用 va_end 之前可以 longjmp 吗?
【发布时间】:2015-08-27 21:13:58
【问题描述】:

在本问答中,您应始终致电va_end()

What exactly is va_end for? Is it always necessary to call it?

但是如果一段代码 longjmp 在你到达 va_end 之前呢? va_end 是否有任何保证会好起来的承诺?或者从概念上讲,它 (例如) 是否会在 va_start() 中分配内存,这会泄露,而不是仅使用堆栈技巧?

【问题讨论】:

  • 我想你需要在调用 longjmp 之前调用 va_end,就像你应该 free() 跳过任何错误分配一样
  • 这不是问题的重点吗?
  • @WeatherVane 是的。问题是如果您处理每个参数项的操作可以 longjmp 怎么办。如果是这样,那么您必须先获取变量 args 并将它们放在另一个地方,然后再进行调用……但是在什么地方?可能是具有大小限制的有界堆栈数组。如果 va_args 已经可以用作那个地方会很好......并且在实际实现中,它通常是。但标准不太可能承诺这一点。
  • @supercat 我在嵌入式系统中也使用了一种残酷的方法作为最后的手段,当所有其他方法都失败但节目必须继续时,它使你提到的部分重启,没有任何悬空。跨度>
  • @supercat 在有或没有多任务的嵌入式系统中我发现最好的方法是一个由计时器驱动的中间“心跳”层,它将服务来自中断的信息并将其呈现给“主循环”确实如此,尽管这是一个简化的陈述。处理错误的最佳方法是将它们从中断作为状态值传递,或者从子程序作为返回值传递。我将跳远视为软件等效于短路。如果一切都失败了,就相当于重置按钮。如果你设计长跳转到基于堆栈的系统,因为你可以,你可耻。

标签: c language-lawyer c89 setjmp variadic-functions


【解决方案1】:

C99 rationale 明确指出va_start 可能分配的内存最终被va_end 释放,这正是您在问题中所猜到的:

7.15.1.2 va_copy

[...]

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

[...]

所以是的,您需要在 longjmp 之前调用 va_end。至少你会在这样的实现中出现内存泄漏。


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

va_startva_end 的实现实际上需要在语法上匹配 va_startva_end,因为它使用了不平衡的大括号,所以 ANSI C 已经不允许这种实现,但同样的原则可以用于在有匹配的大括号时工作。

我几乎找不到关于这个实现的具体信息,它只是 80 年代末、90 年代初在 Usenet 上的点点滴滴。我所发现的一点点可能是不完整的,甚至是完全错误的。非常欢迎提供更多详细信息,尤其是任何自己使用此实现的人。

【讨论】:

  • 我认为还值得引用包含In many implementations, this is a do-nothing operation; but those implementations that need it probably need it badly.va_end 基本原理。 badly 虽然很模糊。
  • “那些需要它的实现可能非常需要它” ...呵呵,很有趣。 :-)
  • @cremno 我试图通过找到一个具体的实现示例来覆盖这一点,其中va_end 不是无操作,但我怀疑我会找到任何。
  • 只是添加当前标准:7.16.1.3p27.16.1.2。但是,它仅指“返回”,而不是 longjmp,也不再指分配的内存。我不太清楚:stdarg.h 是否 not 需要 stdlib.h 或任何其他内存分配函数。那么它仅指堆栈分配/自动内存吗?如果是这样,longjmp 不会正确处理这个问题吗?
  • @Olaf stdarg.h 很容易声明 void __va_end(va_list); 然后拥有 #define va_end(ap) __va_end(ap)。由于__va_end 的主体不需要可见,因此不需要free 等的可见声明来释放内存。
【解决方案2】:

如果您使用的是存储在全局变量中的jmp_buff(通常的模式),那么复制它并使用setjmp 应该是安全的,因此longjmp 将转到您的代码而不是外部调用者;对于longjmp,您的代码可以使用存储的缓冲区副本调用va_endlongjmp;如果您的代码正常退出,则需要在返回之前恢复全局缓冲区。

【讨论】:

  • @HostileFork 实际上,如果您的代码不能在具有非平凡va_end 的平台上运行,那么我可能会忽略这个问题。但是,您可能必须准备好一些东西来处理跳过代码中发生的堆分配。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-24
  • 2010-09-27
  • 1970-01-01
  • 2011-02-18
相关资源
最近更新 更多