有没有一种gcc选项可以假设所有extern "C"函数不能传播异常?

27

除了在每个函数原型上添加属性之外,有没有其他方法让gcc知道C函数永远不会传播异常,即所有声明在extern "C"内的函数都应该是__attribute__((nothrow))?理想情况下,应该有一种类似于-f的命令行选项。


有一个-fnothrow-opt选项,但我认为它不会按照你想要的方式工作。请查看http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#C_002b_002b-Dialect-Options获取更多信息。 - Himanshu
2
此外,extern "C" 函数确实不能抛出异常(具有定义行为)吗?如果是这样,您希望 GCC 在没有选项的情况下进行优化。但是,如果该函数被声明为 extern "C",但(与您的代码不同)在 C++ 中实现,则我认为在实践中它可以抛出异常,只要它实际上没有从 C 调用。我记不清这是否合法。 - Steve Jessop
5
在这里我们遇到了C和“GNU C”之间的区别。 C语言没有异常处理机制,因此即使函数回调到可能是C++代码(例如qsort的情况),也不能期望异常能够跨越外部函数(C)边界传播。然而,gcc开发人员希望将异常作为C语言的一部分(可以使用-fexceptions作为“GNU C”的一部分),并且希望在回调等情况下支持跨C/C++代码边界的异常处理。因此,我们处于这样的混乱境地。 - R.. GitHub STOP HELPING ICE
1
@R..:明白了,谢谢。顺便说一下,在这种情况下,你可以在每个头文件的开头放置#ifndef THAT_ATTRIBUTES #ifdef __cplusplus #define THAT_ATTRIBUTES __attribute__((nothrow)) #else #define THAT_ATTRIBUTES,以避免对common_junk.h的依赖。我理解你反对垃圾代码的立场,不过,写出合适的C代码并期望实现能够合理处理它是更好的选择。 - Steve Jessop
@Radek S:感谢您为此悬赏! - R.. GitHub STOP HELPING ICE
显示剩余2条评论
4个回答

3

您可以始终使用-fno-exceptions,这将确保C++编译器不生成异常传播代码。


1
我从编写 C 库代码并可能从 C++ 调用的角度提出问题。我真的不想在头文件中填充特定于 gcc 或特定于 C++ 的混乱代码,以帮助 gcc 优化调用我的代码的 C++ 调用者;我更愿意只是记录一下,如果您从 C++ 调用它并且不想要不必要的异常开销,则应向 g++ 传递某些选项。如果没有这样的选项,我将满足于让 g++ 生成臃肿的代码。 - R.. GitHub STOP HELPING ICE
9
你确定你正在优化真正的东西吗?现今异常处理的设置已经被优化成仅在抛出异常(不常见情况)时才消耗 CPU 时间,而非在没有异常(常见情况)时消耗。因此,在常见情况下实际上没有什么可以节省的。如果调用者真的关心异常开销,那么这个答案是正确的;对于任何需要快速执行的代码,完全关闭异常处理是正确的选择。 - apenwarr
1
我同意在许多现代系统上支持异常的时间成本为零,但是空间成本(以磁盘上的可执行文件/库大小为单位)非常巨大。 - R.. GitHub STOP HELPING ICE
2
定义“巨大”是没有必要的,因为我认为你在无中生有。除非你是在嵌入式领域工作,否则这并不重要。 - John Bellone
1
R..是正确的;如果您想亲自查看空间成本,请编译一个测试程序,在其中定义一个具有非平凡析构函数的局部变量,然后调用puts()。即使在-O3下,GCC 4.8也拒绝生成明显的直线代码;它总是会生成一个额外的(死)代码路径,以调用_Unwind_Resume。想象一下程序中每个作用域的这些额外的死块,您就会开始了解问题的规模……即使在桌面系统上。(可执行文件的文件大小是一个问题。Icache污染是另一个问题。) - Quuxplusone

2

顺便提一下:
你确定告诉编译器“所有这些函数都不会抛出异常”正是你想要的吗?

并不是说extern "C" ...函数不能传播/触发异常。拿一个例子来说明:

class Foo {
public:
    class Away {};
    static void throwaway(void) { throw Away(); }
}

extern "C" {
    void wrap_a_call(void (*wrapped)(void)) { wrapped(); }
}

int main(int argc, char **argv)
{
    wrap_a_call(Foo::throwaway);
    return 0;
}

编译并运行此代码将创建一个C链接函数wrap_a_call(),当像上面那样调用它时,它会愉快地引发异常:
$ ./test
terminate called after throwing an instance of 'Foo::Away'
Abort(coredump)

即使在C++中使用/调用extern "C"函数,也可能会出现"异常泄漏"(通过调用函数指针),并且仅仅因为你在特定的地方使用/调用extern "C"函数,并不能保证在调用这些函数时不会抛出异常。


1
是的。除非指定了一个与C++异常交互并支持传播的C函数(由于它无法捕获它们,这意味着它没有内存泄漏问题或内部状态可能被longjmp破坏),否则在C++到C到C++调用/回调边界上传播异常已经很危险了(对于C标准库中的函数,这是未定义行为)。 - R.. GitHub STOP HELPING ICE

-1

当异常被抛出时,它会生成中断,这会展开堆栈并覆盖现有的堆栈。它会一直上升到try / except语法所在的点。这意味着如果您不使用异常,则不会有任何开销。唯一的内存/时间开销是在try / catch块和throw()中展开堆栈。

如果您的C函数不会生成异常,则仅在调用C++中的try / catch时存在空间开销,但对于任何数量的异常都是相同的。(以及在初始化此小空间时带有常数的小时间开销)。


-1
GCC 4.5 似乎会自动为我优化这些内容。实际上,这一行出现在 http://gcc.gnu.org/gcc-4.5/changes.html 的更改列表中:
  • GCC 现在优化异常处理代码。特别是被证明没有任何效果的清理区域将被优化掉。

它可以优化掉不必要的代码,但它是否可以优化掉不必要的DWARF展开信息? - R.. GitHub STOP HELPING ICE
我认为它通常适用于 C 函数,但我再次仔细检查后发现它只适用于_inline_函数的情况。 - Samuel Audet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接