首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >对于C标准未定义的以前广泛支持的行为,可以使用哪些替换?

对于C标准未定义的以前广泛支持的行为,可以使用哪些替换?
EN

Stack Overflow用户
提问于 2015-04-15 16:23:47
回答 2查看 352关注 0票数 14

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

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

既然不再支持请求以下操作的“明显”方法,那么在使用旧编译器时,应该如何替换它们,使其不妨碍可读性,也不会对代码生成产生不利影响?为了便于描述,假设int为32位,ui为无符号int,si为有符号int,b为无符号字符。

  1. 给定uib,计算b==0..31的ui << b,或者对于32.255值可以任意表现为ui << (b & 31)或0的值。注意,当右操作数超过31时,如果左手操作数为零,则这两种行为将是相同的。
  2. 对于只需要在右移或左移时产生零的处理器上运行的代码,计算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是否导致事物移动,并通过减去该对象的前基址来确定指针指向死对象的偏移量(注意,调整需要通过计算每个死指针的偏移量来完成,然后将其添加到新指针,而不是试图计算旧指针和新指针之间的“差异”--这在许多分段体系结构上是合理的)。

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

EN

回答 2

Stack Overflow用户

发布于 2015-05-13 20:52:24

现代标准C的指定方式可以保证它是可移植的,前提是您编写代码时不需要比标准C抽象机器给出的底层硬件更多的期望。

对于给定的目标CPU和体系结构,仍然可以为在给定的优化级别上具有特定行为的特定编译器编写,但不要期望任何其他编译器(现代的或其他的,甚至是您所编写的编译器的一个小的修订版),在您的代码违反以下条件时,尝试直观您的期望:标准说,期望任何定义良好的实现不可知论行为是不合理的。

票数 3
EN

Stack Overflow用户

发布于 2016-01-16 05:15:01

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

  • 使用无符号数字的行为通常比有符号数字的行为定义得更好。
  • 将优化问题视为实施质量问题.这意味着,如果您担心某个内部循环的微优化,您应该使用读取编译器的程序集输出(如gcc -S),如果发现它未能优化定义良好的行为以使机器指令合适,则将向编译器的发行者提交一个缺陷报告。(但是,当针对特定平台的唯一实用编译器的发行者对优化不太感兴趣时(例如针对MOS 6502的cc65 ),这是行不通的。

根据这些原则,您通常可以导出一种定义良好的方法来实现相同的结果,然后将信任-但-验证原则应用于生成代码的质量。例如,使转换函数具有定义良好的行为,并让优化器删除架构本身所保证的任何不必要的检查。

代码语言:javascript
复制
// 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,转换为未签名,做数学,并返回到签名。这会产生非诱捕性的、定义明确的行为。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29655664

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档