【发布时间】:2018-05-04 08:33:59
【问题描述】:
我正在尝试编写一个函数来计算一些位标志,同时避免使用分支或条件:
uint8_t count_descriptors(uint8_t n)
{
return
((n & 2) && !(n & 1)) +
((n & 4) && !(n & 1)) +
((n & 8) && !(n & 1)) +
((n & 16) && !(n & 1)) +
((n & 32) && 1 ) +
((n & 64) || (n & 128)) ;
}
bit 0 不直接计数,bit 0 不设置只考虑bit 1-4,bit 5 无条件考虑,bit 6-7 只能计数一次。
但是,我知道布尔值 && 和 ||使用短路评估。这意味着它们的使用会创建一个条件分支,如您在此类示例中所见:if( ptr != nullptr && ptr->predicate()) 保证如果从第一个子表达式对结果进行短路评估,则不会执行第二个子表达式中的代码。
问题的第一部分:我需要做什么吗?由于这些是没有副作用的纯算术运算,编译器会创建条件分支吗?
第二部分:我知道按位布尔运算符不会短路评估,但唯一的问题是位不对齐。屏蔽第 n 位的结果是 2^n 或零。
使 (n & 16) 等表达式计算为 1 或 0 的最佳方法是什么?
【问题讨论】:
-
好的。所以你真的很聪明,可以用比特做聪明的事情。没有其他人会知道您的版本实际上应该做什么(即使您确实注释了它,注释最终也会偏离代码。他们总是这样做。)为什么不把它写清楚可维护的开始方式?
-
Apple LLVM 8.1.0 将例程优化为没有任何分支的代码(针对其默认的 macOS 目标)。但是,这不是您可以依赖的行为。不同的编译器会做不同的事情,甚至一个编译器在不同的情况下也可能会做不同的事情。大多数情况下,您应该编写清晰的代码,并避免让人们或编译器不清楚正在发生的事情。
-
第二部分的答案:
(n & x) << x将为您提供x = 2^k的 0 或 1 -
延伸 Eric 的观点:启用优化的现代编译器非常聪明,可以为此类函数生成最佳代码。避免分支也不一定更快。我只是写了这个函数,这样人们就可以不费吹灰之力地理解它,并让编译器发挥它的魔力。
标签: c++ bit-manipulation bitwise-operators boolean-logic short-circuiting