如何在Windows上使用预处理器执行log(x)?例如:
#define A log(4)/log(2)
在我的代码中
int b[A]; // A=2 will be computed with the preprocessor ! not in run time
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个元素的额外成本似乎是可以接受的,并且可以为您节省相当多的麻烦。所以我只会在必要的时候使用这种诡计。
n
d
IS_REPRESENTIBLE_IN_D_BITS(D, N)
N
D
-1
BITS_TO_REPRESENT(42)
**更新:**为避免混淆:这个解决方案不使用预处理器来计算“对数”。预处理器所做的就是执行一个文本替换,如果使用-E开关进行编译(至少对于GCC),您可以看到这个替换。让我们看看这段代码:
-E
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中。
return
-S
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
kx1ctssn2#
对于LOG宏,使用最多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的幂非常容易。因此,您可以考虑将常量定义为:
log
#define logA 4 #define A (1<<logA)
而不是:
#define A 16 #define logA LOG(A)
jum4pzuy3#
C预处理器#define纯粹是一种文本替换机制。您将无法在编译时计算日志值。你也许可以使用C++模板,但这是我不理解的黑魔法,目前无关紧要。或者,正如我在下面的评论中提到的,您可以构建自己的预处理器,在将更新的代码交给标准C编译器之前评估数组大小等式。
#define
编辑
在我的博客里,我看到了这样一个问题: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足够聪明,可以在这种情况下解决这个问题。所以你的问题的答案可以在尝试中找到,看看你的编译器实际上产生了什么样的代码。
A_CONSTANT
intrinsics
ax6ht2ek4#
这个答案受到了5gon12eder的启发,但使用了更简单的第一个宏。不像5gon12eder的解决方案,这个实现给出了0为BITS_TO_REPRESENT(0),这可以说是正确的。此BITS_TO_REPRESENT(N)函数返回表示小于或等于非负整数N的无符号整数的位数;存储大小为N的带符号数将需要一个附加比特。
0
BITS_TO_REPRESENT(0)
BITS_TO_REPRESENT(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的值。例如:
BITS_TO_REPRESENT
floor(log(N)/log(2))
BITS_TO_REPRESENT(7)
3
floor(log(7)/log(2))
2
BITS_TO_REPRESENT(8)
4
floor(log(8)/log(2))
9fkzdhlc5#
最好的办法是使用c++14constexpr变量模板:
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
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;
这允许我们向枚举中添加新成员,因此不必更新位字段.
6条答案
按热度按时间x8diyxa71#
好了,现在是肮脏的蛮力预处理器技巧。
从你的问题中,我假设你实际上想要的不是一个一般的对数(这在整数算术中甚至是不可能的),而是表示给定数字所需的位数。如果我们把自己限制为32位整数,就有一个解决方案,尽管它不太漂亮。
其思想是,一个数
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),您可以看到这个替换。让我们看看这段代码:它将被预处理为(被警告):
这看起来很糟糕,如果在运行时对其进行评估,它将是相当多的指令。然而,由于所有的操作数都是常量(或者更准确地说是文字),编译器能够在编译时计算它。它必须这样做,因为在C89中数组长度声明必须是一个常量.
如果在不要求是编译时常数的其他地方使用宏,则由编译器决定是否计算表达式。然而,任何合理的编译器都应该执行这种相当初级的优化-称为 * 常量折叠 * -如果启用了优化。如果有疑问--一如既往--请查看生成的汇编代码。
例如,让我们考虑这个方案。
return
语句中的表达式显然不需要是编译时常量,所以让我们看看GCC将生成什么代码。(我使用-S
开关在组装阶段停止。)即使没有启用任何优化,我也会得到以下汇编代码,其中显示宏扩展被折叠到常量6中。
kx1ctssn2#
对于
LOG
宏,使用最多32位整数的简短定义可以是:但是,在使用它之前,请检查您是否真的需要它。人们经常需要对2的幂的值使用对数。例如,当实现位数组等时。虽然很难将
log
计算为常数表达式,但定义2的幂非常容易。因此,您可以考虑将常量定义为:而不是:
jum4pzuy3#
C预处理器
#define
纯粹是一种文本替换机制。您将无法在编译时计算日志值。你也许可以使用C++模板,但这是我不理解的黑魔法,目前无关紧要。
或者,正如我在下面的评论中提到的,您可以构建自己的预处理器,在将更新的代码交给标准C编译器之前评估数组大小等式。
编辑
在我的博客里,我看到了这样一个问题:Do any C or C++ compilers optimize within define macros?
此问题是关于评估此宏字符串的:
共识是
A_CONSTANT
可以是一个编译时间常数,取决于编译器的智能程度,以及什么数学函数被定义为intrinsics
。它还暗示GCC足够聪明,可以在这种情况下解决这个问题。所以你的问题的答案可以在尝试中找到,看看你的编译器实际上产生了什么样的代码。
ax6ht2ek4#
这个答案受到了5gon12eder的启发,但使用了更简单的第一个宏。不像5gon12eder的解决方案,这个实现给出了
0
为BITS_TO_REPRESENT(0)
,这可以说是正确的。此BITS_TO_REPRESENT(N)
函数返回表示小于或等于非负整数N
的无符号整数的位数;存储大小为N
的带符号数将需要一个附加比特。BITS_TO_REPRESENT
是几乎以2为底的对数。由于从浮点到整数的默认转换是截断,因此以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
。BITS_TO_REPRESENT(8)
是4
,而floor(log(8)/log(2))
是3
。9fkzdhlc5#
最好的办法是使用c++14
constexpr
变量模板:Live Example
8iwquhpp6#
下面是最佳答案的修改版本,它做了同样的事情。
帮助宏将1移位x,即我们正在测试的位数。例如,如果我们检查一个数字是否可以用两个比特表示,我们将1移位2,即4。然后我们减去1。此数字是可以用此位数表示的最大值,因此提供的数字必须小于或等于它。对于true和false,我们分别返回1或0。
主宏检查数字是否可以用那么多位表示,如果不能,则链接到下一个三进制子句。如果31不起作用,则假定32位,因为所有其它可能性都被消除。在零的情况下,它将返回1,因为我们总是需要至少一位来存储一些东西。
此宏不能替代log2。它只告诉需要多少位。
常见的用例是位字段结构成员。
这允许我们向枚举中添加新成员,因此不必更新位字段.