C99 Standard是否允许编译器转换代码,以便在满足某些推断条件后不再评估相同的表达式?

时间:2014-05-07 12:56:09

标签: c optimization c99 compiler-optimization

我不太了解5.1.2.3/3的以下部分:

  

如果表达式可以推导出它,那么实际的实现不需要评估表达式的一部分   不使用值,不产生任何副作用(包括由...引起的任何副作用)   调用函数或访问volatile对象。)

假设我有以下代码:

char data[size];
int i;
int found;
/* initialize data to some values in here */
found = 0;
for( i = 0; i < size; i++ ) {
   if( data[i] == 0 ) {
      found = 1;
      /* no break in here */
   }
}
/* i no longer used, do something with "found" here */

请注意,found0开头,可以保持不变或变为1。它不能变成1然后变成别的东西。因此,以下代码将产生相同的结果(除了i值之外,无论如何都不会在循环之后使用):

char data[size];
int i;
int found;
/* initialize data to some values in here */
found = 0;
for( i = 0; i < size; i++ ) {
   if( data[i] == 0 ) {
      found = 1;
      break;
   }
}
/* i no longer used, do something with "found" here */

现在标准所说的关于的内容不需要评估关于found = 1的表达式的部分内容以及循环控制表达式,它们在第一次迭代之后进行控制if

显然,如果在此代码之后的某处使用found,编译器必须发出遍历数组的代码并有条件地评估found = 1表达式。

对于数组中找到的每个零,是否需要对found = 1进行一次评估,或者它是否可以将其评估为一次,因此在编译第一个代码段时会有效地为第二个代码段发出代码?

2 个答案:

答案 0 :(得分:6)

  

它可以改为再一次评估它,因此在编译第一个片段时有效地为第二个片段发出代码吗?

是的,编译器有权执行该优化。这似乎是一个非常积极的优化,但它是合法的。

看一个更符合文本精神的例子可能会很有趣:

  

实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用并且没有产生所需的副作用(包括由调用函数或访问volatile对象引起的任何副作用)。

假设我们有:

int x = pureFunction(y) * otherPureFunction(z);

假设编译器知道两个函数都是int-returns&#34; pure&#34;功能;也就是说,它们没有副作用,它们的结果完全取决于论点。假设编译器还认为otherPureFunction是一个非常昂贵的操作。编译器可以像您编写的那样选择实现代码:

int temp = pureFunction(y);
int x = temp == 0 ? 0 : temp * otherPureFunction(z);

即,确定在某些条件下不必计算otherPureFunction(),因为一旦知道左操作数为零,乘法结果就已知。没有必要的副作用将被省略,因为没有副作用。

答案 1 :(得分:5)

是的,它可以执行此优化,因为没有I / O操作,从volatile位置读取或优化代码省略的外部可见写入内存,因此保留了行为。

作为这种优化的一个例子,GCC将编译

void noop(const char *s)
{
    for (size_t i = 0; i < strlen(s); i++) {
    }
}

到一个完全空的函数:

noop:
.LFB33:
        .cfi_startproc
        rep ret
        .cfi_endproc

允许这样做,因为标准保证strlen的行为,编译器知道它对s或任何其他内存没有外部可见影响,并且可以推断出整个功能没有任何行为。 (令人惊讶的是,这种简单的优化将复杂性从二次变为常数。)