如何使用预处理器计算日志

时间:2014-12-20 15:33:13

标签: c

如何在Windows上使用预处理器执行log(x)?

像:

#define A    log(4)/log(2)

在我的代码之后的数组

int b[A]; // A=2 will be computed with the preprocessor ! not in run time

6 个答案:

答案 0 :(得分:6)

好吧,现在为脏蛮力预处理器诡计。

根据你的问题,我假设你真正想要的不是一般对数(在整数运算中甚至不可能),而是表示给定数字所需的位数。如果我们将自己限制为32位整数,那么就有一个解决方案,尽管它并不漂亮。

#define IS_REPRESENTIBLE_IN_D_BITS(D, N)                \
  (((unsigned long) N >= (1UL << (D - 1)) && (unsigned long) N < (1UL << D)) ? D : -1)

#define BITS_TO_REPRESENT(N)                            \
  (N == 0 ? 1 : (31                                     \
                 + IS_REPRESENTIBLE_IN_D_BITS( 1, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS( 2, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS( 3, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS( 4, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS( 5, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS( 6, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS( 7, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS( 8, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS( 9, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(10, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(11, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(12, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(13, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(14, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(15, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(16, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(17, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(18, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(19, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(20, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(21, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(22, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(23, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(24, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(25, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(26, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(27, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(28, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(29, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(30, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(31, N)    \
                 + IS_REPRESENTIBLE_IN_D_BITS(32, N)    \
                 )                                      \
   )

这个想法是一个数字 n &gt;当且仅当 n ≥2 d -1 时,0表示使用精确的 d 位。 n &lt; 2 d 。在特别处理了 n = 0的情况之后,我们只是对所有32个可能的答案进行暴力破解。

辅助宏IS_REPRESENTIBLE_IN_D_BITS(D, N)将扩展为评估为D的表达式,如果N可以使用D位完全表示,则-1BITS_TO_REPRESENT(42)。我已经定义了宏,如果答案为“否”,则结果为-1。为了补偿负数,我最后加31。如果数字不能用任何1,...,32位表示,那么整体结果将为-1,这有助于我们发现一些错误。

表达式-E是一个有效的编译时常量,用于数组长度声明。

所有这一切,总是使你的阵列32个元素长的额外成本似乎可以为许多应用程序所接受,它可以为你省去一些麻烦。所以,如果我真的不得不这样做,我只会使用这种技巧。

更新:为避免混淆:此解决方案使用预处理器来评估“对数”。所有预处理器都执行文本替换,如果使用int main() { int digits[BITS_TO_REPRESENT(42)]; return 0; } 开关进行编译(至少对于GCC),您可以看到。我们来看看这段代码:

int
main()
{
  int digits[(42 == 0 ? 1 : (31 + (((unsigned long) 42 >= (1UL << (1 - 1)) && (unsigned long) 42 < (1UL << 1)) ? 1 : -1) + (((unsigned long) 42 >= (1UL << (2 - 1)) && (unsigned long) 42 < (1UL << 2)) ? 2 : -1) + (((unsigned long) 42 >= (1UL << (3 - 1)) && (unsigned long) 42 < (1UL << 3)) ? 3 : -1) + (((unsigned long) 42 >= (1UL << (4 - 1)) && (unsigned long) 42 < (1UL << 4)) ? 4 : -1) + (((unsigned long) 42 >= (1UL << (5 - 1)) && (unsigned long) 42 < (1UL << 5)) ? 5 : -1) + (((unsigned long) 42 >= (1UL << (6 - 1)) && (unsigned long) 42 < (1UL << 6)) ? 6 : -1) + (((unsigned long) 42 >= (1UL << (7 - 1)) && (unsigned long) 42 < (1UL << 7)) ? 7 : -1) + (((unsigned long) 42 >= (1UL << (8 - 1)) && (unsigned long) 42 < (1UL << 8)) ? 8 : -1) + (((unsigned long) 42 >= (1UL << (9 - 1)) && (unsigned long) 42 < (1UL << 9)) ? 9 : -1) + (((unsigned long) 42 >= (1UL << (10 - 1)) && (unsigned long) 42 < (1UL << 10)) ? 10 : -1) + (((unsigned long) 42 >= (1UL << (11 - 1)) && (unsigned long) 42 < (1UL << 11)) ? 11 : -1) + (((unsigned long) 42 >= (1UL << (12 - 1)) && (unsigned long) 42 < (1UL << 12)) ? 12 : -1) + (((unsigned long) 42 >= (1UL << (13 - 1)) && (unsigned long) 42 < (1UL << 13)) ? 13 : -1) + (((unsigned long) 42 >= (1UL << (14 - 1)) && (unsigned long) 42 < (1UL << 14)) ? 14 : -1) + (((unsigned long) 42 >= (1UL << (15 - 1)) && (unsigned long) 42 < (1UL << 15)) ? 15 : -1) + (((unsigned long) 42 >= (1UL << (16 - 1)) && (unsigned long) 42 < (1UL << 16)) ? 16 : -1) + (((unsigned long) 42 >= (1UL << (17 - 1)) && (unsigned long) 42 < (1UL << 17)) ? 17 : -1) + (((unsigned long) 42 >= (1UL << (18 - 1)) && (unsigned long) 42 < (1UL << 18)) ? 18 : -1) + (((unsigned long) 42 >= (1UL << (19 - 1)) && (unsigned long) 42 < (1UL << 19)) ? 19 : -1) + (((unsigned long) 42 >= (1UL << (20 - 1)) && (unsigned long) 42 < (1UL << 20)) ? 20 : -1) + (((unsigned long) 42 >= (1UL << (21 - 1)) && (unsigned long) 42 < (1UL << 21)) ? 21 : -1) + (((unsigned long) 42 >= (1UL << (22 - 1)) && (unsigned long) 42 < (1UL << 22)) ? 22 : -1) + (((unsigned long) 42 >= (1UL << (23 - 1)) && (unsigned long) 42 < (1UL << 23)) ? 23 : -1) + (((unsigned long) 42 >= (1UL << (24 - 1)) && (unsigned long) 42 < (1UL << 24)) ? 24 : -1) + (((unsigned long) 42 >= (1UL << (25 - 1)) && (unsigned long) 42 < (1UL << 25)) ? 25 : -1) + (((unsigned long) 42 >= (1UL << (26 - 1)) && (unsigned long) 42 < (1UL << 26)) ? 26 : -1) + (((unsigned long) 42 >= (1UL << (27 - 1)) && (unsigned long) 42 < (1UL << 27)) ? 27 : -1) + (((unsigned long) 42 >= (1UL << (28 - 1)) && (unsigned long) 42 < (1UL << 28)) ? 28 : -1) + (((unsigned long) 42 >= (1UL << (29 - 1)) && (unsigned long) 42 < (1UL << 29)) ? 29 : -1) + (((unsigned long) 42 >= (1UL << (30 - 1)) && (unsigned long) 42 < (1UL << 30)) ? 30 : -1) + (((unsigned long) 42 >= (1UL << (31 - 1)) && (unsigned long) 42 < (1UL << 31)) ? 31 : -1) + (((unsigned long) 42 >= (1UL << (32 - 1)) && (unsigned long) 42 < (1UL << 32)) ? 32 : -1) ) )];
  return 0;
}

它将被预处理(被警告):

int
main()
{
  return BITS_TO_REPRESENT(42);
}

这看起来很糟糕,如果在运行时进行评估,那将是相当多的指令。但是,由于所有操作数都是常量(或文字,确切地说),编译器能够在编译时对其进行评估。它必须这样做,因为数组长度声明必须是C 89中的常量。

如果在其他不需要编译时常量的地方使用宏,则由编译器决定是否计算表达式。但是,如果启用了优化,任何合理的编译器都应该执行这种相当基本的优化 - 称为常量折叠。如果有疑问 - 一如既往 - 请查看生成的汇编代码。

例如,让我们考虑一下这个程序。

return

-S语句中的表达式显然不需要是编译时常量,所以让我们看看GCC将生成什么代码。 (我正在使用main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl $6, %eax # See the constant 6? popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc 开关在装配阶段停止。)

即使没有启用任何优化,我也会得到以下汇编代码,表明宏扩展已经折叠成常量6。

{{1}}

答案 1 :(得分:3)

使用高达32位的整数的LOG宏的定义稍微简短一点:

#define LOG_1(n) (((n) >= 2) ? 1 : 0)
#define LOG_2(n) (((n) >= 1<<2) ? (2 + LOG_1((n)>>2)) : LOG_1(n))
#define LOG_4(n) (((n) >= 1<<4) ? (4 + LOG_2((n)>>4)) : LOG_2(n))
#define LOG_8(n) (((n) >= 1<<8) ? (8 + LOG_4((n)>>8)) : LOG_4(n))
#define LOG(n)   (((n) >= 1<<16) ? (16 + LOG_8((n)>>16)) : LOG_8(n))

但是,在使用之前,请检查您是否真的需要它。人们经常需要使用对数值作为2的幂。例如,当实现位数组时。虽然很难将log计算为常量表达式,但很容易定义2的幂。因此,您可以考虑将常量定义为:

#define logA   4
#define A      (1<<logA)

而不是:

#define A     16
#define logA  LOG(A)

答案 2 :(得分:2)

C预处理器#define纯粹是一种文本替换机制。您将无法在编译时计算日志值。

你可能能够使用C ++模板,但这是我不理解的黑魔法,而且目前无关紧要。

或者正如我在下面的评论中提到的,您可以构建自己的预处理器,在将更新的代码交给标准C编译器之前评估数组大小方程式。

修改

在探索更多时,我看到了这个问题:Do any C or C++ compilers optimize within define macros?

这个问题是关于评估这个宏字符串:

#include <math.h>
#define ROWS 15
#define COLS 16
#define COEFF 0.15
#define NODES (ROWS*COLS)
#define A_CONSTANT (COEFF*(sqrt(NODES)))

共识是A_CONSTANT可以是编译时常数,取决于编译器的智能程度,以及哪些数学函数定义为intrinsics。它还提到海湾合作委员会足够聪明,可以在这种情况下解决这个问题。

因此,您可以在尝试问题时找到问题的答案,并查看编译器实际生成的代码类型。

答案 3 :(得分:2)

这个答案的灵感来自5gon12eder,但有一个更简单的第一个宏。与5gon12eder的解决方案不同,此实现为0提供BITS_TO_REPRESENT(0),这可以说是正确的。此BITS_TO_REPRESENT(N)函数返回表示小于或等于非负整数N的无符号整数的位数;存储有符号数量的N将需要一个额外的位。

#define NEEDS_BIT(N, B)     (((unsigned long)N >> B) > 0)

#define BITS_TO_REPRESENT(N)                            \
        (NEEDS_BIT(N,  0) + NEEDS_BIT(N,  1) + \
         NEEDS_BIT(N,  2) + NEEDS_BIT(N,  3) + \
         NEEDS_BIT(N,  4) + NEEDS_BIT(N,  5) + \
         NEEDS_BIT(N,  6) + NEEDS_BIT(N,  7) + \
         NEEDS_BIT(N,  8) + NEEDS_BIT(N,  9) + \
         NEEDS_BIT(N, 10) + NEEDS_BIT(N, 11) + \
         NEEDS_BIT(N, 12) + NEEDS_BIT(N, 13) + \
         NEEDS_BIT(N, 14) + NEEDS_BIT(N, 15) + \
         NEEDS_BIT(N, 16) + NEEDS_BIT(N, 17) + \
         NEEDS_BIT(N, 18) + NEEDS_BIT(N, 19) + \
         NEEDS_BIT(N, 20) + NEEDS_BIT(N, 21) + \
         NEEDS_BIT(N, 22) + NEEDS_BIT(N, 23) + \
         NEEDS_BIT(N, 24) + NEEDS_BIT(N, 25) + \
         NEEDS_BIT(N, 26) + NEEDS_BIT(N, 27) + \
         NEEDS_BIT(N, 28) + NEEDS_BIT(N, 29) + \
         NEEDS_BIT(N, 30) + NEEDS_BIT(N, 31)   \
        )

BITS_TO_REPRESENT 几乎基数为2的对数。由于从浮点到整数的默认转换是截断,因此base-2对数的整数版本对应于浮点计算floor(log(N)/log(2))BITS_TO_REPRESENT(N)会返回大于floor(log(N)/log(2))的内容。

例如:

  1. BITS_TO_REPRESENT(7)3,而floor(log(7)/log(2))2
  2. BITS_TO_REPRESENT(8)4,而floor(log(8)/log(2))3

答案 4 :(得分:0)

最好的选择,使用 constexpr变量模板:

template <unsigned int x>
constexpr enable_if_t<x != 0, int> log2 = 1 + log2<x / 2U>;

template <>
constexpr int log2<1U> = 0;

Live Example

答案 5 :(得分:-1)

预处理后,您的数组声明将如下所示

int  b[log(4)/log(2)];

这是有效的数组声明吗?在数组声明中,您应该有常量表达式,并且log(4)/log(2)将在运行时计算。