c ++编译时断言

时间:2011-03-03 16:06:35

标签: c++ boost assertions

我正在使用一个可能很危险的宏:

#define REMAINDER(v, size) ((v) & (size -1))

显然它假设大小是2的幂。

我想确保大小确实是2的幂,但是在编译时。 (在运行时测试很容易,但不是我想要的)。

对我来说,一个充分的测试就是大小总是一个常数(从不变量)。

我会使用BOOST_STATIC_ASSERT,但我无法弄清楚如何将它用于我需要的地方。

5 个答案:

答案 0 :(得分:6)

首先要做的事情是:不需要微观优化。当a % b是编译时常量(实际上是2的幂)时,任何启用了优化的正常编译器都会将b转换为该构造。

然后在特定断言上,您可以使用相同的构造来断言它[*]:

BOOST_STATIC_ASSERT( !(size & (size-1)) );

[*]请注意,正如Matthieu M指出的那样,只有size是无符号类型才有效。应该断言 - 在编译时不能断言参数是非负的较小要求:

BOOST_STATIC_ASSERT( (X(0)-1) > X(0) ); // where X is the type of the argument

最后评论后编辑:

你错过了这一点。要使静态断言宏起作用,size必须是编译时常量。如果它是一个编译时常量,那么只要在定义常量时断言 这也是最好的地方,因为它将作为文档,并指向需要修改的代码的精确点: / p>

template <typename N>
class hash_map {
public:
   const std::size_t size = N;
   BOOST_STATIC_ASSERT( !(size & (size-1) ) ); // N must be power of 2 for fast %
   //...
};

同时声明在编译时保持不变量对于效率很重要,模糊代码不是:只需保留模运算,因为编译器将优化:

std::size_t hash_map::index_of( std::size_t hash ) const {
   return hash % size;
}

因为size是一个编译时常量,并且它是2的幂(你之前断言过),优化器会将%转换为优化操作,而代码仍然是人类可读的需要维护它。

答案 1 :(得分:5)

编辑:除非您有分析证明这是您的代码中的瓶颈并且您的编译器没有适当地优化(v%8),只需编写明显的代码。

否则,因为你在这里处理整数,你可以使用模板而不是宏吗?然后你应该能够在模板中静态断言,以便在它被错误地实例化时标记。

例如:

template <int size>
int remainder(int v)
{
    BOOST_STATIC_ASSERT(!(size & (size - 1)));

    return v & (size -1);
};

答案 2 :(得分:3)

断言:

size && (size & (size - 1) == 0)

编辑:解释。

如果size是2的幂,则只设置一位。减1将产生一个值,其中所有位都设置为原始位。 ANDing这些给出0。

如果大小为2的幂,则至少设置两位。减1将产生一个值,其中所有位都设置为原始的最低设置位。对这些进行AND运算不会产生0,因为仍然设置了第二个和更晚的(从右)位。

1000 & 0111 == 0000
1100 & 1011 == 1000

答案 3 :(得分:2)

// Only use this if size is a power of 2
#define REMAINDER(v, size) ((v) & (size -1))

不要像白痴一样对待您的用户。记录您的功能和宏,然后继续。


或者如果您真的必须愚蠢地溺爱人,请使用模板:

template <size_t SIZE>
size_t remainder(size_t v) {
   // Perform whatever assertions you like on SIZE here

   return v & (SIZE - 1);
}

请注意,我必须对输入和输出的类型做出一些假设。

答案 4 :(得分:1)

Peephole Optimization:针对一小组指令执行优化

这包括:

  • Constant Folding:例如,如果size - 1不变,则会在编译期间评估size
  • Strength Reduction:其中包括用较快的操作替换慢操作...当结果相同时(显然)

现在,让我们使用LLVM backend optimizer

查看此示例
// C
int iremainder(int i) { return i % 4; }

unsigned uremainder(unsigned u) { return u % 4; }

// LLVM IR
define i32 @iremainder(i32 %i) nounwind readnone {
entry:
  %0 = srem i32 %i, 4                             ; <i32> [#uses=1]
  ret i32 %0
}

define i32 @uremainder(i32 %u) nounwind readnone {
entry:
  %0 = and i32 %u, 3                              ; <i32> [#uses=1]
  ret i32 %0
}

让我们分析,好吗?

  • u / 4产生and i32 %u, 3(在C中翻译为u & 3
  • i / 4产生srem i32 %i, 4(在C中翻译为i % 4

什么是智能编译器!它不会忘记对有符号整数执行按位运算不会产生我想要的结果(只要整数是负数,并且分支比分支更昂贵)。

士气:这种优化几乎没用,你甚至搞错了。