对于以前广泛支持的行为,有哪些替代品可用于C标准未定义的行为

sup*_*cat 14 c undefined-behavior language-lawyer

在标准化之前的C早期,实现有各种方法来处理各种操作的异常和半异常情况.如果没有先配置,其中一些会触发可能导致随机代码执行的陷阱.因为这些陷阱的行为超出了C标准的范围(并且在某些情况下可能由运行程序控制之外的操作系统控制),并且避免要求编译器不允许依赖此类陷阱的代码陷阱继续这样做,可能导致此类陷阱的操作行为完全取决于编译器/平台的判断.

到20世纪90年代末,虽然C标准没有要求这样做,但每个主流编译器都采用了许多这些情况的共同行为; 使用这样的行为将允许在代码速度,大小和可读性方面进行改进.

由于不再支持请求以下操作的"明显"方式,因此在使用较旧的编译器时,应如何以不妨碍可读性的方式替换它们,也不会对代码生成产生负面影响?出于描述的目的,假设int是32位,ui是unsigned int,si是signed int,并且b是unsigned char.

  1. 给定uib计算ui << bb == 0..31,或者ui << (b & 31)对于值32..255 可以任意表现为零的值.请注意,如果右侧操作数超过31时左侧操作数为零,则两种行为都是相同的.

  2. 对于只需要在右移或左移32到255时产生零的处理器上运行的代码,计算ui << bb == 0..31和0 = b == 32..255.虽然编译器可能能够优化设计为跳过值32..255的移位的条件逻辑(因此代码只会执行将产生正确行为的移位),但我不知道如何制定这样的条件逻辑这将保证编译器不会为它生成不必要的代码.

  3. 与1和2一样,但是对于右移.

  4. 鉴于si并且b这样b0..30并且si*(1<<b)不会溢出,计算si*(1<<b).请注意,使用乘法运算符会严重损害许多旧编译器的性能,但如果移位的目的是缩放有符号值,则在操作数在整个移位期间保持负值的情况下转换为无符号会感觉错误.

  5. 给定各种整数值,执行加法,减法,乘法和移位,如果没有溢出,结果将是正确的,并且如果存在溢出,代码将产生其高位表现为非陷阱和非陷阱的值. -UB但是以其他方式不确定时尚或将以可识别的平台定义的方式陷阱(并且在不支持陷阱的平台上,将简单地产生不确定的价值).

  6. 给定指向已分配区域的指针以及指向其中内容的指针,用于realloc更改分配大小并调整上述指针以匹配,同时避免在realloc返回原始块的情况下的额外工作.不一定可能在所有平台上,但是90年代主流平台都会允许代码确定是否realloc导致事物移动,并通过减去该对象的前一个基地址来确定指针到死对象的偏移量(注意)调整需要通过计算与每个死指针相关联的偏移量,然后将其添加到新指针,而不是通过尝试计算旧指针和新指针之间的"差异" - 这将在许多情况下合法地失败分段架构).

"超现代"编译器是否为上述提供了任何良好的替代品,这些替代品不会降低代码大小,速度或可读性中的至少一个,而在任何其他代码中都没有改进?据我所知,整个20世纪90年代99%的编译器不仅可以完成上述所有工作,而且对于每个例子,人们都能够以相同的方式在几乎所有编译器上编写代码.一些编译器可能试图用无人看守的跳转表来优化左移和右移,但这是我能想到的唯一一种情况,即20世纪90年代的20世纪90年代平台编译器对"明显"的编码方式有任何问题任何上述内容.如果超现代编译器不再支持经典形式,它们作为替代品提供什么?

Gre*_*ods 3

现代标准 C 的指定方式是这样的:当且仅当您编写代码时对其运行的底层硬件没有比 C 抽象机隐式和显式给出的标准更多的期望,才能保证它是可移植的描述。

您仍然可以为特定编译器编写代码,该编译器在给定目标 CPU 和架构的给定优化级别上具有特定行为,但不要期望任何其他编译器(现代或其他编译器,甚至是您编写的编译器的较小修订版)如果您的代码违反了标准规定期望任何明确定义的实现不可知行为是不合理的条件,则要竭尽全力尝试凭直觉了解您的期望。