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

时间:2015-04-15 16:23:47

标签: c language-lawyer undefined-behavior

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

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

因为"显而易见"不再支持请求以下操作的方法,如何在不使用旧编译器的情况下以不妨碍可读性和对代码生成产生负面影响的方式更换它们?出于描述的目的,假设int是32位,ui是无符号整数,si是有符号整数,b是无符号字符。

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

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

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

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

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

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

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

2 个答案:

答案 0 :(得分:2)

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

您仍然可以编写针对给定目标CPU和体系结构在给定优化级别具有特定行为的特定编译器,但期望任何其他编译器(现代或其他,甚至是如果您的代码违反了标准规定期望任何明确定义的实现不可知行为是不合理的条件,那么就为您试图满足您的期望而不断修改您的期望。

答案 1 :(得分:0)

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

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

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

// 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,施放到无符号,进行数学计算,然后再回到签名状态。这会产生非陷阱明确定义的行为。