反正有没有在C中绕过这个编译器优化?

时间:2015-07-04 17:30:55

标签: c bit compiler-optimization bit-shift

我想要注意的是,正如Olaf所指出的那样,编译器没有错。

免责声明:我不完全确定这种行为是由编译器优化引起的。

无论如何,在C中我试图确定8位字节的第n位(n应该在0和7之间,包括0和7)是1还是0。我最初想出了这个解决方案:

#include <stdint.h>
#include <stdbool.h>

bool one_or_zero( uint8_t t, uint8_t n ) // t is some byte, n signifies which bit
{
    return (t << (n - (n % 8) - 1)) >> 7;
}

根据我之前的理解,将对字节执行以下操作:

假设t = 5n = 2。然后,字节t可以表示为0000 0101。我假设(t << (n - (n % 8) - 1))会改变t的位,以便t1010 0000。这个假设只是有些正确。我还假设下一位移位(>> 7)会移位t的位,以便t0000 0001。这个假设也只是有些正确。

TL; DR :我认为第return (t << (n - (n % 8) - 1)) >> 7;行是这样做的:

  1. t0000 0101
  2. 发生第一次移位; t现在是1010 0000
  3. 发生第二次移位; t现在是0000 0001
  4. t0000 0001
  5. 的形式返回

    虽然我打算这样做,但事实并非如此。相反,我必须写下以下内容,以获得我的预期结果:

    bool one_or_zero( uint8_t t, uint8_t n ) // t is some byte, n signifies which bit
    {
        uint8_t val = (t << (n - (n % 8) - 1));
        return val >> 7;
    }
    

    我知道添加uint8_t val并不是一个巨大的性能消耗。不过,我想知道两件事:

    1. 我是否必须初始化另一个变量来执行我想要的操作?
    2. 为什么单行内容与双行内容的作用相同?
    3. 我的印象是,当编译器优化我的代码时,它会将两个位移一起打破,因此只发生一个。这似乎是一件好事,但它没有按照预期“清除”其他位。

2 个答案:

答案 0 :(得分:7)

这个代码非常复杂,只是检查一个整数位。尝试标准方法:

return (t & (1U << n)) != 0;

如果必须检查n是否有效,请添加断言。 else掩蔽(n & 7)或模数(n % 8)(这将由编译器优化到掩码操作)将强制移位计数在有效范围内。由于该模式将被许多编译器识别,如果可用,它们可能会将其转换为单个位测试CPU指令。

为避免幻数,您应该将模数8替换为:(sizeof(t) * CHAR_BIT)。这将遵循t可能具有的任何类型。掩模总是小于模数。

您的代码:

(n - (n % 8) - 1))

如果n < 8它会产生负值(-1正好)。 Negative shifts现在undefined behaviour,所以任何事情都可能发生(注意鼻子恶魔)。

答案 1 :(得分:0)

我相信你是整数推广的牺牲品。

当你有一个表达式:x operator y时,你应该注意一些事情。第一个是表达式的结果(并且在其他操作数的过程中)被提升为&#34;最大的&#34;两个操作数的类型。

在您的示例中,这意味着以下内容:

  1. (t << (n - (n % 8) - 1)) >> 7;常数8是int因此n%8也是int
  2. (t << (n - (integer) - 1)) >> 7 (n - integer - 1)也是一个整数,这意味着临时值(t << integer)将存储在int中。这意味着你不会切断&#34;你想要的最重要的一点,因为结果存储在(很可能)32位,而不像你想象的那样存储。
  3. 另一方面,如果您暂时将int结果存储在uint8_t中,您将正确地切断前导位并获得您想要的结果。

    您可以通过在计算过程中将操作数转换为uint8_t来解决此问题:

    (t << (uint8_t)(n - (n % 8) - 1))) >> 7;
    

    或者甚至更好,使用奥拉夫在答案中建议的面具:

    (t & ((uint8_t)1 << n)) != 0