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

标签 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

最佳答案

好的,现在是肮脏的蛮力预处理器诡计。

根据您的问题,我假设您真正想要的不是一般对数(这在整数算术中甚至不可能),而是表示给定数字所需的位数。如果我们将自己限制为 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 ≥ 2d−1 且 n < 2d 时,n > 0 的数字具有使用恰好 d 位的表示。在对 n = 0 的情况进行特殊处理后,我们简单地对所有 32 个可能的答案进行了暴力破解。

辅助宏 IS_REPRESENTIBLE_IN_D_BITS(D, N)将扩展为评估为 D 的表达式如果 N可以精确地表示为 D位和到 -1除此以外。我已经定义了宏,如果答案为“否”,则结果为 -1。为了补偿负数,我在最后添加了 31。如果数字不能用任何 1, ..., 32 位表示,那么总体结果将是 -1,这应该有助于我们发现一些错误。

表达式 BITS_TO_REPRESENT(42)是用于数组长度声明的有效编译时常量。

综上所述,对于许多应用程序来说,总是使数组长度为 32 个元素的额外成本似乎是可以接受的,它为您节省了很多麻烦。因此,如果我真的必须这样做,我只会使用这种技巧。

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

它将被预处理为(被警告):
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;
}

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

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

例如,让我们考虑这个程序。
int
main()
{
  return BITS_TO_REPRESENT(42);
}
return 中的表达式语句显然不需要是编译时常量,所以让我们看看 GCC 将生成什么代码。 (我正在使用 -S 开关在组装阶段停止。)

即使没有启用任何优化,我也会得到以下汇编代码,它显示宏扩展被折叠到常量 6 中。
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

关于c - 如何使用预处理器计算日志,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27581671/

相关文章:

c - 来自 "The C Programming Language"的代码如何工作?

c - 在 C 中保存分数

c - 当我执行 printf ("%d",'=' +'=' ); 时,为什么它显示 122。请详细解释

c - tolower 用于 C 风格的字符串

c - 用指针影响字符串

c - C中的send()函数如果没有发送所有字节如何处理?

c++ - 函数存储在内存的哪一段?

c - C中的工作分配算法(do while issue)

c - 切换 MSB 的最佳方式是什么?

C++ 命令行调试参数