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

uxhixvfz  于 2023-05-28  发布在  其他
关注(0)|答案(6)|浏览(112)

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

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

在我的代码中

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

x8diyxa71#

好了,现在是肮脏的蛮力预处理器技巧。
从你的问题中,我假设你实际上想要的不是一个一般的对数(这在整数算术中甚至是不可能的),而是表示给定数字所需的位数。如果我们把自己限制为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> 0有一个精确使用d位的表示,当且仅当n ≥ 2x 1 m3n1x −1且n <2x 1 m5n1x。在特别处理了n = 0的情况后,我们简单地对所有32个可能的答案进行了暴力破解。
辅助宏IS_REPRESENTIBLE_IN_D_BITS(D, N)将扩展为一个表达式,如果N可以精确地使用D位表示,则计算为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;
}

这看起来很糟糕,如果在运行时对其进行评估,它将是相当多的指令。然而,由于所有的操作数都是常量(或者更准确地说是文字),编译器能够在编译时计算它。它必须这样做,因为在C89中数组长度声明必须是一个常量.
如果在不要求是编译时常数的其他地方使用宏,则由编译器决定是否计算表达式。然而,任何合理的编译器都应该执行这种相当初级的优化-称为 * 常量折叠 * -如果启用了优化。如果有疑问--一如既往--请查看生成的汇编代码。
例如,让我们考虑这个方案。

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
kx1ctssn

kx1ctssn2#

对于LOG宏,使用最多32位整数的简短定义可以是:

#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)
jum4pzuy

jum4pzuy3#

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。它还暗示GCC足够聪明,可以在这种情况下解决这个问题。
所以你的问题的答案可以在尝试中找到,看看你的编译器实际上产生了什么样的代码。

ax6ht2ek

ax6ht2ek4#

这个答案受到了5gon12eder的启发,但使用了更简单的第一个宏。不像5gon12eder的解决方案,这个实现给出了0BITS_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为底的对数。由于从浮点到整数的默认转换是截断,因此以2为底的对数的整数版本对应于浮点计算floor(log(N)/log(2))BITS_TO_REPRESENT(N)返回比floor(log(N)/log(2))大1的值。
例如:

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

9fkzdhlc5#

最好的办法是使用c++14constexpr变量模板:

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

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

Live Example

8iwquhpp

8iwquhpp6#

下面是最佳答案的修改版本,它做了同样的事情。

#define REPRESENT_IN_X_BITS(n, x)\
(\
    ( ((1U<<x)-1U) >= (n) ) ? 1 : 0\
)

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

// First we check if the number is zero or one. In either case, we will need
// one bit to represent them.
// Then we check if it can be represented with that number of bits and return
// that exact number if so.
//
// If it could not be represented with 32 bits, it is assumed to be 32
// since we have exhausted all other possibilities.

帮助宏将1移位x,即我们正在测试的位数。例如,如果我们检查一个数字是否可以用两个比特表示,我们将1移位2,即4。然后我们减去1。此数字是可以用此位数表示的最大值,因此提供的数字必须小于或等于它。对于true和false,我们分别返回1或0。
主宏检查数字是否可以用那么多位表示,如果不能,则链接到下一个三进制子句。如果31不起作用,则假定32位,因为所有其它可能性都被消除。在零的情况下,它将返回1,因为我们总是需要至少一位来存储一些东西。
此宏不能替代log2。它只告诉需要多少位。
常见的用例是位字段结构成员。

enum {
    myenum_0
    myenum_1
    myenum_2
    myenum_opts
};
// Note: Untested
typedef struct {
    int i:BITS_NEEDED(myenum_opts-1);
}example;

这允许我们向枚举中添加新成员,因此不必更新位字段.

相关问题