为什么C++编译器无法优化"if(test)--foo"到"foo - = test"?

Bor*_*rov 6 c++ g++ compiler-optimization

我有一个函数,它找到给定整数的下一个2的幂.如果整数是2的幂,则返回功率.

非常直截了当:

char nextpow2if(int a)
{
    char foo = char(32 - __builtin_clz(a));
    bool ispow2 = !(a & a-1);
    if (ispow2) --foo;
    return foo;
}
Run Code Online (Sandbox Code Playgroud)

但是在使用gcc 6和-O2进行编译之后,在检查生成的程序集之后,我发现cmovne在计算foo-1之后,这是用看似无用的指令编译的.更糟糕的是gcc5和更老版本我jne在代码中得到了一个实际的分支.

编译它的更快方法就像我编写了以下函数:

char nextpow2sub(int a)
{
    char foo = char(32 - __builtin_clz(a));
    bool ispow2 = !(a & a-1);
    return foo - ispow2;
}
Run Code Online (Sandbox Code Playgroud)

所有编译器都将此代码正确编译为最短(和最快)可能的程序集,sete并使用bool的减法.

为什么编译器无法优化第一个?这似乎是一个非常容易识别的案例.为什么gcc 5和更早版本将它编译成实际的jne分支?两个版本之间是否存在边缘情况,我看不到,这可能导致它们的行为不同?

PS:现场演示在这里

编辑:我没有用gcc 6测试性能,但是使用gcc 5,后者的速度提高了两倍(至少在合成性能测试中).这实际上是让我提出这个问题的原因.

hau*_*ron 0

我相信其原因可能是bool通常存储在一个字节内。因此,编译器可能无法安全地假设实际内存恰好等于1。true/false检查可能只是与零进行比较。然而,减法可能是一个不同的故事,有副作用。

请参阅Ideone 上的示例代码

#include <iostream>
using namespace std;

union charBool
{
    unsigned char aChar;
    bool aBool;
};

int main() 
{
    charBool var;
    charBool* varMemory = &var;

    var.aBool = 65;
    std::cout << "a boolean = " << var.aBool << std::endl;
    std::cout << "a char = " << var.aChar << std::endl;
    std::cout << "varMemory = " << (*(reinterpret_cast<unsigned char*>(varMemory))) << std::endl;

    var.aChar = 98;   // note: Ideone C++ compiler resolves this to zero, hence bit0 seems to be the only checked
    std::cout << "a boolean = " << var.aBool << std::endl;
    std::cout << "a char = " << var.aChar << std::endl;
    std::cout << "varMemory = " << (*(reinterpret_cast<unsigned char*>(varMemory))) << std::endl;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

结果是:

a boolean = 1
a char = 
varMemory = 
a boolean = 0
a char = b
varMemory = b
Run Code Online (Sandbox Code Playgroud)

(注:前两个字符不可打印)