C宏在编译时求值

4

我需要一个宏,在编译时被评估,类似于:

#define FIND_RANGE(x) \
if x>16 \
32 \
elif x>8 \
16 \
elif x>4 \
8 \
elif x>2 \
4 \
elif x>1 \
2 \
else \
1 \
endif \

所以这段代码。
#define S1 FIND_RANGE(7)
unsinged int i = S1;

将被发送到编译器作为

unsinged int i = 8;

这个简单的算法能否在编译时进行评估?


1
据我所知,你不能在标准的C或C++中完成这个任务,但是gcc的预处理器有这个功能,也许可以帮到你: - Jabberwocky
2
(x > 16 ? 32 : x > 8 ? 16 : x > 4 ? 8 等) - M.M
1
看起来你想要将数字向上舍入至下一个2的幂次方,或者更具体地说,例如 - Sander De Dycker
@M.M 那个有效,非常感谢。 - Danijel
4个回答

9

虽然C语言没有constexpr函数,但是GCC和Clang可以通过-O1选项对简单函数进行编译时计算。相关的优化称为常量折叠

以下是C语言代码:

#include <stdio.h>

static inline unsigned int findRange(unsigned int x)
{
    if (x > 16)
        return 32;
    else if (x > 8)
        return 16;
    else if (x > 4)
        return 8;
    else if (x > 2)
        return 4;
    else if (x > 1)
        return 2;
    return 1;
}

int main(void)
{
    unsigned int i = findRange(7);
    printf("%u\n", i);
    return 0;
}

将其转换为x86-64汇编代码的结果如下(参考:godbolt.org/g/kVYe0u):
main:
        sub     rsp, 8
        mov     esi, 8
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        add     rsp, 8
        ret

正如您所看到的,对于findRange的调用被替换为编译时计算出的值。

即使findRange被定义为带有外部链接的普通(非内联)函数也可以使用此方法。


感谢您的努力。有一个更简单的解决方案,请参考M.M的建议。 - Danijel
2
@Danijel:M.M的建议很不错,它允许将条件打包到单个表达式中,而不是多个语句中。我的回答的重点是证明,现代编译器会优化代码(产生最终结果),同时代码更易读且更安全(例如考虑SCALE(x ++))。 - Grzegorz Szpetkowski

3

我认为这并不容易。问题在于预处理器可用的条件语句是以预处理指令的形式出现的。

然而,您可以巧妙地使用#include指令来创建更高级的结构。创建find-range.mac如下:

#if x>16
32
#elif x>8
16
#elif x>4
8
#elif x>2
4
#elif x>1
2
#else
1
#endif
#undef x

然后将其用作:

int i = 
#define x 7
#include "find-range.mac"
;

应该扩展为类似于以下内容:

int i =
8
;

另外一个不完全的技巧是将 FIND_RANGE(x) 替换成 FIND_RANGEx 并定义适当的 FIND_RANGEx。这需要 x 被限制在一个有限的值集合中:

#define FIND_RANGE(x) FIND_RANGE ## x
#define FIND_RANGE1 1
#define FIND_RANGE2 2
#define FIND_RANGE3 4
#define FIND_RANGE4 4
#define FIND_RANGE5 8
#define FIND_RANGE6 8
#define FIND_RANGE7 8
#define FIND_RANGE8 8
// etc...

2
为了消遣一下,我将Sander提到的那个位操作技巧翻译成了一个宏。
#define XS(x,y) (x | (x>>y))
#define FR(x) XS(XS(XS(XS(XS(x-1,1),2),4),8),16)+1

所以FR(7)在编译时应该给出8,依此类推。
(但实际上,Grzegorz Szpetkowski的答案是值得参考的。)

抱歉,我没有时间测试这个。由于速度不是问题,我使用了更直接的解决方案:(x > 16 ? 32 : x > 8 ? 16 : x > 4 ? 8等。 - Danijel
当然,我也绝不会将上述宏用于除概念验证之外的任何事情。 - dbrank0

1
原来这是可行的,甚至很简单:

#define POW00          1.0f
#define POW01          2.0f
#define POW02          4.0f
#define POW03          8.0f
#define POW04         16.0f
#define POW05         32.0f
#define POW06         64.0f
#define POW07        128.0f
#define POW08        256.0f  // use some nicer pow2 constant generation

#define SCALE(x) ( \
x > POW07 ? POW08 : \
x > POW06 ? POW07 : \
x > POW05 ? POW06 : \
x > POW04 ? POW05 : \
x > POW03 ? POW04 : \
x > POW02 ? POW03 : \
x > POW01 ? POW02 : \
x > POW00 ? POW01 : POW00 \
) // end SCALE

例子:

int main()
{
   float a = (float)SCALE(7.0f);
}

这个在编译时被评估为:
float a = 8.0f;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接