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

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

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

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

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

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

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

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

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

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

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

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

您仍然可以编写针对给定目标CPU和体系结构在给定优化级别具有特定行为的特定编译器,但是不要期望任何其他编译器(现代或其他编译器,或者甚至是您编写的那个的小修订版)如果您的代码违反了标准规定期望任何明确定义的实现不可知行为是不合理的条件,那么请尽量试图直观地追求您的期望。

两个一般原则适用于标准C和标准C ++:

  • 无符号数的行为通常比带有有符号数的行为更好地定义。
  • 将优化视为实施质量问题。 这意味着如果您担心特定内循环的微优化,您应该阅读编译器的汇编输出 (例如使用gcc -S ),如果您发现它无法优化明确定义的行为适当的机器指令,向编译器的发布者提交缺陷报告 。 (但是,当针对特定平台的唯一实用编译器的发布者对优化不太感兴趣时,例如cc65定位MOS 6502),这不起作用。)

根据这些原则,您通常可以获得一种定义明确的方法来实现相同的结果,然后将信任 – validation原则应用于生成的代码的质量。 例如,使用明确定义的行为制作移位函数,并让优化器删除架构本身保证的任何不需要的检查。

 // Performs 2 for unsigned numbers. Also works for signed // numbers due to rule for casting between signed and unsigned // integer types. inline uint32_t lsl32(uint32_t ui, unsigned int b) { if (b >= 32) return 0; return ui << b; } // Performs 3 for unsigned numbers. inline uint32_t lsr32(uint32_t ui, unsigned int b) { if (b >= 32) return 0; return ui >> b; } // Performs 3 for signed numbers. inline int32_t asr32(int32_t si, unsigned int b) { if (si >= 0) return lsr32(si, b); if (b >= 31) return -1; return ~(~(uint32)si >> b); } 

对于4和5,转换为无符号,进行数学运算,然后转回签名。 这会产生非陷阱明确定义的行为。