是否依赖于GCC / LLVM的`-fexceptions`技术上未定义的行为?

问题描述 投票:1回答:3

据我所知,编译器扩展可能被认为是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代码进行堆栈展开的两个相当冗长的讨论:

c++ c exception gcc undefined-behavior
3个回答
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
投票

在某种意义上,C没有定义当你调用用C语言编写的函数时会发生什么,更不用说如果该函数无法返回但是以其他方式结束其生命周期和C调用者的生命周期会发生什么,是的,它是未定义的行为。它不是“实现定义的行为”,因为实现定义的行为的定义特征是语言标准对它们记录特定行为的实现施加了要求,而这不是这种情况;有关主题完全超出相关标准的范围。

从合理和可移植的C编程的角度来看,你不应该使用或依赖于从C调用的-fexceptions和C ++代码应该捕获最外层extern "C"函数中的所有异常(或通过函数指针暴露给C调用者的函数)和将它们转换为错误代码或与C兼容的某种机制(例如longjmp,但只有在记录为C调用者必须为被调用者做好准备时才会这样做)。


1
投票

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

因此,如果您使用相同的代码并在纯标准C ++中编译它,那么该代码将展示UB。但是如果你使用gcc / clang扩展在C ++中编译它,那么代码就可以很好地定义了。

© www.soinside.com 2019 - 2024. All rights reserved.