c - 四舍五入到 2 的幂(使用预处理器常量)

标签 c macros

使用 unsigned int 可以像这样四舍五入到 2 的幂:

unsigned int power_of_2_max_u(unsigned int x)
{
    x -= 1;
    x = x | (x >> 1);
    x = x | (x >> 2);
    x = x | (x >> 4);
    x = x | (x >> 8);
    x = x | (x >>16);
    return x + 1;
}

或者……

int is_power_of_2_i(int n)
{
    return (n & (n - 1)) == 0;
}

int power_of_2_max_i(int n)
{
    if (is_power_of_2_i(n))
        return n;

    do {
        n = n & (n - 1);
    } while (!is_power_of_2_i(n));

    return n * 2;
}

然而,当值是常量时,应该可以使用预处理器来避免每次都计算该值。 e.例如:

i = power_of_2_max_u(sizeof(SomeStruct));

这是一个等价于 power_of_2_max_u 的宏。

#define POW2_CEIL(v) (1 + \
(((((((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) | \
     ((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) >> 0x04))) | \
   ((((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) | \
     ((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) >> 0x04))) >> 0x02))) | \
 ((((((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) | \
     ((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) >> 0x04))) | \
   ((((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) | \
     ((((v) - 1) | (((v) - 1) >> 0x10) | \
      (((v) - 1) | (((v) - 1) >> 0x10) >> 0x08)) >> 0x04))) >> 0x02))) >> 0x01))))

由这个 Python-3 脚本生成。

import math
BITS = 32
data = (["(v - 1)"] +
        ["(v | (v >> 0x%02x))" % i
         for i in reversed([2 ** j
         for j in range(int(math.log2(BITS)))])])
macro = "1 + v"
for l in reversed(data):
    macro = macro.replace('v', '(%s)' % l)
print("#define POW2_CEIL(v)", macro) 

使用 statement expressions它可以做得更好一些,但这依赖于 GCC 扩展。

#define POW2_CEIL(x_) ({      \
    unsigned int x = x_; \
    x -= 1;              \
    x = x | (x >> 1);    \
    x = x | (x >> 2);    \
    x = x | (x >> 4);    \
    x = x | (x >> 8);    \
    x = x | (x >>16);    \
    x + 1; })

虽然这是一个相当丑陋的宏,但我仔细检查了一下,编译器确实将它正确地缩减为一个常量(它应该如此)所以 unsigned int a = 8; 完全等同于 unsigned int a = POW2_CEIL(7);, 但是我很想知道是否有更好的方法来做到这一点(可能不限于 int 范围)。

最佳答案

However when the value is a constant, it should be possible to use the preprocessor to avoid having to calculate the value every time.

可读性>>>性能。首先,问问自己“”这是我的代码中真正的瓶颈吗?

如果答案是“否”,那么什么都不做;继续前进,不要考虑过早地对您的代码进行微“优化”。

即使这是您代码的重要部分,请考虑您的函数是纯函数。它们的返回值仅取决于它们的参数,因为它们执行简单的计算(基本上是映射)。 很有可能,任何体面的优化编译器在使用编译时已知的表达式调用时都会不断折叠您的函数。

例如下面的代码……

#include <stdio.h>

static unsigned power_of_2_max_u(unsigned x)
{
    x -= 1;
    x = x | (x >> 1);
    x = x | (x >> 2);
    x = x | (x >> 4);
    x = x | (x >> 8);
    x = x | (x >>16);
    return x + 1;
}

struct Foo {
    int x;
    char c;
    int i;
};

int main()
{
    printf("%zu %u\n", sizeof(struct Foo), power_of_2_max_u(sizeof(struct Foo)));

    return 0;
}

…由clang -O2 -flto翻译成以下程序集:

/tmp/constfold:
(__TEXT,__text) section
_main:
0000000100000f50    pushq   %rbp
0000000100000f51    movq    %rsp, %rbp
0000000100000f54    leaq    0x37(%rip), %rdi  ## literal pool for: "%zu %u\n"
0000000100000f5b    movl    $0xc, %esi        ## *** sizeof(struct Foo) == 12
0000000100000f60    movl    $0x10, %edx       ## *** rounded up == 16
0000000100000f65    xorl    %eax, %eax
0000000100000f67    callq   0x100000f70       ## symbol stub for: _printf
0000000100000f6c    xorl    %eax, %eax
0000000100000f6e    popq    %rbp
0000000100000f6f    retq

因此,您不需要为编译器执行任何操作以常量折叠您的函数。

仅当您观察到它们常量折叠时,才考虑将您的函数重写为上述宏之一(或使用不可移植的编译器内部函数)。

关于c - 四舍五入到 2 的幂(使用预处理器常量),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22925016/

相关文章:

c - 在 Windows 上访问 in6_addr 成员

c - 在不涉及功能的情况下打印到标准输出

c - 数学函数在现代处理器上需要多少周期

c - 如何在C程序中设置CURLOPT_HTTPHEADER的值,以便在HTTP Post请求中不发送HTTP Header?

当一个宏调用另一个宏时的 Clojure 宏和默认参数

c++ - 换行符在 C/C++ 中意味着什么?

c++ - 是否有预处理器宏来防止其他人在 C++ 中包含私有(private) header ?

使用宏时 C 结构数组初始化问题

macros - 如何让Clojure的 `while`返回一个值

c - 用于在 C 中创建关键字的预处理器指令