编译器省略外循环

时间:2014-10-02 10:10:03

标签: c++ visual-c++

我在C ++项目中实现了(读取:从wiki复制并粘贴)XXTEA密码。为清楚起见,我在单独的函数中分离了加密和解密:(注意:这不是加密问题!请不要对所选密码进行评论)

#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
static void btea_enc( unsigned int *v, unsigned n, const unsigned int* key ) {
    unsigned int y, z, sum;
    unsigned p, rounds, e;
    rounds = 16 + 52/n;
    sum = 0;
    z = v[n-1];
    do {
        sum += DELTA;
        e = (sum >> 2) & 3;
        for (p=0; p<n-1; p++) {
            y = v[p+1]; 
            z = v[p] += MX;
        }
        y = v[0];
        z = v[n-1] += MX;
    } while (--rounds);
}
static void btea_dec( unsigned int *v, unsigned n, const unsigned int* key ) {
    unsigned int y, z, sum;
    unsigned p, rounds, e;
    rounds = 16 + 52/n;
    sum = rounds*DELTA;
    y = v[0];
    do {
        e = (sum >> 2) & 3;
        for (p=n-1; p>0; p--) {
            z = v[p-1];
            y = v[p] -= MX;
        }
        z = v[n-1];
        y = v[0] -= MX;
    } while ((sum -= DELTA) != 0);
}
#undef MX
#undef DELTA

当在Debug中编译此代码时,它完美地工作。但是,当我使用Visual Studio 2013(v120)使用(默认)优化编译此代码时,btea_dec会丢失其外部循环(导致解密产生垃圾)。

encryptiondecryption的反汇编列表。注意解密期间缺少外部循环! (如果您希望代码为文本,我很乐意上传,它只是一面文字)

查看实际代码,结束条件是溢出的无符号整数'sum':
while ((sum -= DELTA) != 0)
我不明白编译器做了什么让它认为它可以摆脱这个循环(afaik溢出只是整数未定义,无符号溢出完全没问题。)

问题:为什么编译器“优化”外环?我该如何解决?

MCVE :(在包含&amp; main之间粘贴包含btea_enc和btea_dec的前一个代码块)

#define _CRT_RAND_S
#include <cstdlib>

int main(int argc, char* argv[])
{
    // Random key
    unsigned int key[4];
    rand_s(&key[0]);
    rand_s(&key[1]);
    rand_s(&key[2]);
    rand_s(&key[3]);

    // Buffer we'll be encrypting
    unsigned int utext[4];
    memcpy(utext, "SecretPlaintext", 16);

    // Encrypt
    btea_enc(utext, 4, key);

    // Decrypt
    btea_dec(utext, 4, key);

    // Should still be equal!
    bool s = !strcmp((char*)utext, "SecretPlaintext");

    // Print message
    printf("Compared: %s\n", s ? "equal" : "falsly");

    return s?0:1;
}

3 个答案:

答案 0 :(得分:4)

通过/ GL,编译器知道n == 4,因此知道rounds == 29。它肯定会预先计算sum的初始值,即rounds*DELTA

接下来,它可能会尝试计算循环迭代次数并展开外循环。如果它做错了(正如我在其他答案中所做的那样),它可能正在做uint32_t(rounds * DELTA) / DELTA,这是一个。添加第一次迭代作为do-while,这就是外部循环所在的位置。

gnasher的循环控制代码更容易让编译器弄清楚,确实有rounds(29)次迭代,它可能会或可能不会决定展开,但是这个数字很难搞清楚迭代。

答案 1 :(得分:1)

为什么这种情况发生在我身边。您可以尝试替换

} while ((sum -= DELTA) != 0);

    sum -= DELTA;
} while ((--rounds) != 0);

答案 2 :(得分:1)

第1步:MACRO的编程风格非常糟糕。用参数重写它,比如

#define MX(key,sum,p,e,y,z) (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

以便有人阅读您的代码实际上可以看到表达式中出现了哪些变量。

更好的是,使用内联函数。


编译器可以优化掉嵌套循环,因为可见的副作用是可预测的。

唯一可见的副作用是v[p] -= MX;,这是可预测的次数。因此编译器可以用v[p] -= loopcount * MX;

替换嵌套循环

ezy被重复写入但从未读过,因此编译器可以完全消除它们及其计算。

请注意,无效操作的这种优化可能会让您感觉到您认为已经小心消除的计时攻击。

整个功能体变为

int p=n;
int subtrahend = rounds * DELTA / DELTA * MX;
do {
   v[--p] -= subtrahend;
} while (p);

<击>