This article提供了一个示例C代码片段,由于没有为循环计数器类型定义溢出,编译器可以对其进行优化.

以下是带有 comments 的代码片段

for (i = 0; i <= N; ++i) { ... }

在此循环中,如果溢出时未定义"i",则编译器可以假定循环将恰好迭代N+1次,这允许广泛的循环优化.另一方面,如果变量被定义为在溢出时绕回,那么编译器必须假设循环可能是无限的(如果N是INT_MAX就会发生)-这将禁用这些重要的循环优化.这特别影响64位平台,因为很多代码都使用"int"作为归纳变量.

我有不少疑问:

  • 我知道,如果溢出时未定义i的类型,编译器可以假定i不会回绕,但为什么这意味着循环运行N+1次?难道不能在循环体中更改i吗?
  • 即使考虑到这一假设,它还能在此基础上进行什么优化呢?例如,如果在编译时不知道N,那么循环就不能被展开,对吗?
  • "大范围的循环优化"让我觉得我在这里错过了很多.

推荐答案

难道我不能在循环的主体中被改变吗?

不,他们的意思是:

size_t N = getSize();
for (int i = 0; i <= N; ++i) { 
   // i never changed here
}

假设是64位平台,N可能太大了,循环无法正常运行.(也许程序员知道N永远不会太大,但编译器不知道).但是如果N太大,i就会溢出,溢出是不可能发生的.因此,编译器可以假设N始终不会太大--即使有时会太大.

相反,

for (unsigned i = 0; i <= N; ++i) {

在这里,即使N是最大可表示的unsigned,i也不会溢出,而是环绕,这是完全合法的.编译器不能再假设N永远不会太大,并且必须处理可能不会结束的循环.

在此基础上,它可以做什么优化?

Just watch this in awe.

如果编译器知道循环将要结束,它就能够使用(可能)更有效的指令集.机器代码变得更长、更复杂,但谁会在乎它是否会为我们每次运行赢得3.27皮秒呢?

C++相关问答推荐

为什么在传输 Big Data 时共享内存段的运行时间比管道更长?

什么C代码将确定打开的套接字正在使用的网络适配器?

Tiva TM4C123GXL的I2C通信

数据包未从DPDK端口传输到内核端口

为什么在C中进行大量的位移位?

为什么cudaFree不需要数据 struct 的地址?

在我的代码中,我需要在哪里编写输出函数?

`#if`条件中是否允许`sizeof`?

如何有效地编写代码来判断两个元素数量相同的数组即使在不同的位置也具有相同的元素?

S在本文中的价值观到底出了什么问题?

在libwget中启用Cookie会导致分段故障

将回调/基于事件的C API转换为非回调API

C:Assignment中的链表赋值从指针目标类型中丢弃‘const’限定符

问题:C#Define上的初始值设定项元素不是常量

为什么一个在线编译器拒绝这个VLA代码,而本地的Apple clang却不拒绝;t?

访问未对齐联合的成员是否为未定义行为,即使被访问的成员已充分对齐?

在同一范围内对具有相同类型的变量执行的相同操作在同一C代码中花费的时间不同

struct 中的qsort,但排序后的 struct 很乱

添加/删除链表中的第一个元素

为什么实现文件中的自由函数默认没有内部链接?