将枚举器添加到枚举中是否会破坏 ABI

Does adding enumerators into enum break ABI?

本文关键字:枚举 ABI 是否 添加      更新时间:2023-10-16

特别是,我在库界面中得到了以下代码:

typedef enum
{
    state1,
    state2,
    state3,
    state4,
    state5,
    state_error = -1,
} State;

我严禁破坏 ABI。但是,我想添加状态 6 和状态 7。它会打破 ABI 吗?

在这里找到了一些提示,但我有点怀疑这是否是我的情况?

您可以。。。

  • 将新枚举器追加到现有枚举。

Exeption:如果这导致编译器为枚举选择更大的底层类型,则使更改二进制不兼容。不幸的是,编译器在选择底层类型方面有一些回旋余地,因此从 API 设计的角度来看,建议添加 Max...。具有显式大值(=255、=1<<15 等(的枚举器,用于创建数字枚举器值的间隔,该间隔保证适合所选的基础类型,无论该类型是什么。

您的问题是一个很好的例子,为什么长期保持 ABI 兼容性是一项艰巨的任务。这里问题的核心是兼容性不仅取决于给定的类型,还取决于它在函数/方法原型或复杂类型(例如结构、联合等(中的使用方式。

(1( 如果枚举严格用作库的输入(例如,作为函数的参数,它只是更改函数/库的行为(,那么它保持兼容性:您以一种永远不会伤害客户的方式更改了合同,即调用应用程序。旧应用程序永远不会使用新值,并且将获得旧行为,新应用程序只会获得更多选项。

(2( 如果枚举在任何地方用作库的输出(例如,返回值或函数填充调用方提供的某些地址,也称为输出参数(,则更改将破坏 ABI。将枚举视为一个协定,声明"应用程序永远不会看到列出的值以外的值"。添加新的枚举成员会破坏此协定,因为旧应用程序现在可以看到它们从未计算过的值。

如果没有

措施保护旧应用程序免于陷入这些麻烦,至少是这样。一般来说,库仍然可以输出新值,但永远不会输出旧应用程序可能提供的任何有效输入。

有一些设计模式允许这样的枚举扩展:

例如,该库可以提供初始化功能,允许指定应用程序准备好的 ABI 版本。旧应用程序要求版本 1.0,并且永远不会在输入时获得新值;较新的应用程序指定 1.1。或 2.0 或如果在版本 1.1 中添加的新枚举值,则它可能获得新值。

或者,如果函数DoSomething()在输入上获取一些标志,则可以添加一个新标志,应用程序可以在其中指定它已准备好查看新的输出值。

或者,如果这是不可能的,新版本的库可能会添加一个新的函数DoSomethingEx(),它提供了比原始DoSomething()更复杂的行为。 DoSomethingEx()现在可以返回新的枚举值,DoSomething()不能。

作为旁注,如果您需要添加此类DoSomethingEx(),请以将来允许类似扩展的方式进行。为了保持一致性,通常最好将其设计为具有默认标志(通常为零(的DoSomethingEx()的行为方式相同DoSomething()并且仅使用某些新标志时,它才提供不同且更复杂的行为。

当然,缺点是库实现必须检查应用程序已准备好的内容,并提供与旧应用程序的期望兼容的行为。看起来没有那么多,但随着时间的推移和库的许多版本,库实现中可能会累积数十个这样的检查,使其更复杂且更难维护。

报价实际上是你的情况。只需在末尾添加新的枚举值(但在state_error之前,因为它具有不同的值(,它应该是二进制兼容的,除非,如您提供的报价中所述,编译器选择使用不同大小的类型,这在如此小的枚举的情况下似乎不太可能。

最好的方法是尝试检查:在更改之前和之后执行一个简单的sizeof(State)应该就足够了(尽管您可能还想检查值是否仍然相同(。

查看最高值的枚举器-id:state3为 2。

这意味着,即使编译器应该选择char作为基础类型,您也可以轻松地在那里容纳 100+ 个额外的枚举器 ID,而不会有损坏二进制兼容性的风险。

这预先假定用户提供迭代器的值,而不是读取一个