有没有办法绕过 C 中的编译器优化?
Is there anyway to get around this compiler optimization in C?
我想指出,正如 Olaf 所指出的,编译器没有错。
免责声明:我不完全确定此行为是由于编译器优化造成的。
无论如何,在 C 中,我正在尝试确定 8 位字节的第 n 位(n 应介于 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 = 5
和 n = 2
。那么字节t
可以表示为0000 0101
。我假设 (t << (n - (n % 8) - 1))
会移动 t
的位,这样 t
就是 1010 0000
。这个假设只是在一定程度上是正确的。我还假设下一个位移位 (>> 7
) 会移动 t
的位,这样 t
就是 0000 0001
。这个假设也只是在一定程度上是正确的。
TL;DR:我认为 return (t << (n - (n % 8) - 1)) >> 7;
行是这样做的:
t
是 0000 0101
- 第一次位移发生;
t
现在是 1010 0000
- 发生第二次位移;
t
现在是 0000 0001
t
返回为 0000 0001
虽然我打算这样做,但事实并非如此。相反,我必须编写以下内容才能获得预期结果:
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
不会消耗大量性能。不过,我想知道两件事:
- 我是否必须初始化另一个变量来执行我想要的操作?
- 为什么单线和双线的效果不一样?
我的印象是,当编译器优化我的代码时,它将两个位移位合并在一起,所以只发生了一个。这似乎是一件好事,但它并没有 "clear" 其他位。
该代码非常复杂,只是检查整数中的一位。尝试标准方法:
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 present undefined behaviour,所以任何事情都有可能发生(小心鼻恶魔)。
我相信你是整数提升的受害者
当您有一个表达式时:x operator y
有几件事您应该注意。首先是将表达式的结果(以及过程中的另一个操作数)提升为两个操作数的 "largest" 类型。
在您的示例中,这意味着以下内容:
(t << (n - (n % 8) - 1)) >> 7;
常数 8 是一个 int
因此 n%8
也是一个 int
.
(t << (n - (integer) - 1)) >> 7
(n - integer - 1)
也是一个整数,也就是说临时值(t << integer)
会存储在一个int
中。这意味着您不会 "cut off" 最重要的位,因为结果存储在(最有可能的)32 位中,而不是您假设的 8 位。
另一方面,如果您暂时将 int
结果存储在 uint8_t
中,您将正确地切断前导位并得到您想要的结果。
您可以通过在计算期间将操作数转换为 uint8_t 来解决此问题:
(t << (uint8_t)(n - (n % 8) - 1))) >> 7;
或者更好的是,使用奥拉夫在回答中建议的面具:
(t & ((uint8_t)1 << n)) != 0
我想指出,正如 Olaf 所指出的,编译器没有错。
免责声明:我不完全确定此行为是由于编译器优化造成的。
无论如何,在 C 中,我正在尝试确定 8 位字节的第 n 位(n 应介于 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 = 5
和 n = 2
。那么字节t
可以表示为0000 0101
。我假设 (t << (n - (n % 8) - 1))
会移动 t
的位,这样 t
就是 1010 0000
。这个假设只是在一定程度上是正确的。我还假设下一个位移位 (>> 7
) 会移动 t
的位,这样 t
就是 0000 0001
。这个假设也只是在一定程度上是正确的。
TL;DR:我认为 return (t << (n - (n % 8) - 1)) >> 7;
行是这样做的:
t
是0000 0101
- 第一次位移发生;
t
现在是1010 0000
- 发生第二次位移;
t
现在是0000 0001
t
返回为0000 0001
虽然我打算这样做,但事实并非如此。相反,我必须编写以下内容才能获得预期结果:
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
不会消耗大量性能。不过,我想知道两件事:
- 我是否必须初始化另一个变量来执行我想要的操作?
- 为什么单线和双线的效果不一样?
我的印象是,当编译器优化我的代码时,它将两个位移位合并在一起,所以只发生了一个。这似乎是一件好事,但它并没有 "clear" 其他位。
该代码非常复杂,只是检查整数中的一位。尝试标准方法:
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 present undefined behaviour,所以任何事情都有可能发生(小心鼻恶魔)。
我相信你是整数提升的受害者
当您有一个表达式时:x operator y
有几件事您应该注意。首先是将表达式的结果(以及过程中的另一个操作数)提升为两个操作数的 "largest" 类型。
在您的示例中,这意味着以下内容:
(t << (n - (n % 8) - 1)) >> 7;
常数 8 是一个int
因此n%8
也是一个int
.(t << (n - (integer) - 1)) >> 7
(n - integer - 1)
也是一个整数,也就是说临时值(t << integer)
会存储在一个int
中。这意味着您不会 "cut off" 最重要的位,因为结果存储在(最有可能的)32 位中,而不是您假设的 8 位。
另一方面,如果您暂时将 int
结果存储在 uint8_t
中,您将正确地切断前导位并得到您想要的结果。
您可以通过在计算期间将操作数转换为 uint8_t 来解决此问题:
(t << (uint8_t)(n - (n % 8) - 1))) >> 7;
或者更好的是,使用奥拉夫在回答中建议的面具:
(t & ((uint8_t)1 << n)) != 0