依靠GCC / LLVM的-fexceptions技术上未定义的行为吗?

时间:2019-04-23 19:41:16

标签: c++ c exception gcc undefined-behavior

据我所知,编译器扩展可以视为undefined rather than implementation-defined。我猜测(但不确定)这是否适用于C ++标准以及C标准。

GCC和LLVM都提供了-fexceptions功能,该功能旨在确保从C ++代码到C代码引发异常,然后在C ++代码中捕获它,这将按预期进行,即在C和C ++中展开堆栈帧,并为C ++本地调用析构函数。 (注意:我知道在释放的C堆栈帧中分配的资源不会释放。这不是我的问题。)这是GCC documentation中的相关文本:

  

如果未指定此选项,则GCC在默认情况下会为通常需要异常处理的C ++之类的语言启用该选项,而对于通常不需要它的C之类的语言将其禁用。但是,在编译需要与用C ++编写的异常处理程序正确互操作的C代码时,可能需要启用此选项。

但是,我在C或C ++标准中找不到任何内容,表明应该期望堆栈展开与包含从不同源语言编译的帧的堆栈交互。 C ++标准似乎只在15.2 except.ctor中提到了展开,它只是解释了在引发异常时销毁本地对象的规则。

因此,即使使用旨在使其以良好定义的方式工作的语言扩展,也会通过C代码未定义的行为传递异常吗?使用这样的实现提供的扩展是“错误的”吗?

就上下文而言,此问题的灵感来自Rust社区中有关C语言堆栈展开的两个相当长的讨论:

3 个答案:

答案 0 :(得分:2)

从某种意义上说,当您调用用C以外的语言编写的函数时,C并未定义会发生什么,而如果该函数无法返回却结束了它的生存期和C的生存期,则发生的事情要少得多呼叫者以其他某种方式,是的,这是未定义的行为不是“实现定义的行为”,因为实现定义的行为的定义特征是语言标准对实现施加了要求,即它们记录了特定的行为,在此情况并非如此;该主题完全超出了相关标准的范围。

从合理且可移植的C编程的角度出发,您不应使用或依赖-fexceptions,而打算从C调用的C ++代码应捕获最外部的extern "C"函数中的所有异常(或通过向C调用者提供的函数指针公开的函数)并将它们转换为错误代码或与C兼容的某种机制(例如longjmp,但前提是必须证明C调用者必须为被调用者做好准备)。

答案 1 :(得分:2)

依靠实施文档

这里的基本问题是我们是否可以依靠C或C ++实现提供的规范。 (由于我们正在处理C和C ++混合代码的情况,因此我将这种组合实现称为单个实现。)

实际上,我们必须依靠实现文档。除非并且直到实现断言(至少部分地)符合标准,否则C和C ++标准才适用。这些标准没有法律效力;除非有人决定采用它们,否则它们不适用于任何人或企业。 (C 2018前言引用了ISO statement,解释了该标准是自愿的。)

如果一个实现告诉您它符合C和C ++标准,并且还告诉您它支持通过C代码抛出C ++异常,则没有理由相信一个,而不是另一个。如果您接受实现的文档,那么它既符合语言标准,也支持通过C代码引发异常。如果您不接受实施文档,则没有理由期望符合语言标准。 (这是一个普遍的看法,而忽略了明显的错误使我们有理由怀疑特定行为的实例。)

如果您询问在C或C ++标准中使用的意义上说,通过C代码传递异常是否“未定义”,答案是肯定的。但是这些标准只是 ,它们讨论了它们的定义。他们对“未定义”的使用并不禁止其他人定义行为。实际上,如果您使用的是实现的文档,则可以为行为定义一个定义。 C和C ++标准不会撤消,否定或取消其他文档所做的定义:

  • C或C ++标准表示未定义任何行为,仅表示该行为在C或C ++标准的上下文内未定义。
  • 程序员选择使用的其他任何规范都可以定义C或C ++标准未定义的其他行为。 C和C ++标准不禁止这样做。

示例

例如,一些可能用来指定商业软件产品行为的文档包括:

  • C标准。
  • C ++标准。
  • 汇编程序手册。
  • 编译器文档。
  • Apple的开发人员工具文档,包括Xcode的行为,链接器以及软件构建期间使用的其他工具。
  • 处理器手册。
  • 指令集体系结构规范。
  • 浮点算法的IEEE-754标准。
  • 用于命令行工具的Unix文档。
  • 用于系统接口的Unix文档。

对于许多软件,如果整体行为未由所有这些规范共同定义,则将无法生产该软件。 C或C ++标准优先于其他文档或优于其他文档的想法是荒谬的。

编写可移植代码

任何软件项目或任何工程项目都可在前提条件下工作:给定各种工具规格,材料特性,设备特性等,并从这些前提条件中获取所需的产品。很少有任何完整的最终用户商业产品完全依赖C或C ++标准。购买iPhone时,它会遵守物理法则,您有权依赖它来遵守电子设备的安全规范以及政府机构规定的射频行为。它符合许多规范,并且C标准应被视为优于其他规范的概念是荒谬的。如果您的设备由于C标准说存在未定义行为的编程错误而引起爆炸,那是不可接受的-C标准说未定义的事实并不能超过安全规范。

即使在纯软件项目中,也很少有严格符合C或C ++标准的产品。在很大程度上,只有严格执行C或C ++的软件才可以执行一些纯计算并限制输入/输出。它可能包括其他软件中包含的非常有用的库,但其中包含的商业最终用户程序很少,例如数学家和科学家用来回答有关逻辑,数学和建模问题的一些东西。这个世界上的大多数软件都以C或C ++标准未定义的方式与设备和操作系统进行交互。大多数软件使用标准未定义的扩展名-这些扩展以标准未定义的方式操作文件和内存,并以标准未定义的方式与设备和用户交互。它们显示GUI窗口并接受用户的鼠标和键盘输入。它们通过网络发送和接收数据。他们将无线电波发送到其他设备。

如果不使用语言标准未定义的行为,这些事情是不可能的。而且,如果语言标准胜过这些行为的定义,那么编写此类软件将是不可能的。如果您想发送Wi-Fi无线电信号,并且您采用了C标准,而C标准胜过其他定义,那么这意味着您不可能编写可靠地发送无线电信号的软件。显然,这是不正确的。 C标准不会胜过其他规范。

对于大多数软件项目而言,编写“便携式代码”并不是可行的要求。当然,希望包含非便携式代码以清除接口。期望写出可以使用可移植代码的代码,以便可以重复使用。但这只是大多数项目的一部分。对于大多数项目,整个项目必须使用语言标准以外的其他文档定义的行为。

答案 2 :(得分:1)

代码不是UB,因为代码不是 C ++ 语言,代码是 C ++,具有gcc / clang 扩展语言。在带有gcc / clang扩展名的C ++中,代码已记录并定义良好。在C ++中,相同的代码将是UB。

因此,如果您采用相同的代码并以纯标准C ++进行编译,则该代码将显示UB。但是,如果您使用带有gcc / clang扩展名的C ++进行编译,则代码定义明确。