提问人:Cinverse 提问时间:2/13/2023 更新时间:5/9/2023 访问量:180
是否可以在 C 语言中编译时计算处理器值的阶乘值?
Is it possible to compute factorial value of a proprocessor value during compile time in C?
问:
#define num 7 \\ user can change this
#define size ???? \\I want this value (factorial of num) to be computed during compile time
int array[size][num];
我想全局定义,但它的大小取决于预处理器的值。因此,我希望在编译时确定值(阶乘)。array
num
num
可能吗?如果是,如何?
答:
在单独的 .h 文件(例如 fc.h)中:
#if num == 1
#define sum 1
#elif num == 2
#define sum 2
#elif num == 3
#define sum 6
#elif num == 4
#define sum 24
#elif num == 5
#define sum 120
#elif num == 6
#define sum 720
#elif num == 7
#define sum 5040
#elif num == 8
#define sum 40320
#elif num == 9
#define sum 362880
#else
#error wrong number
#endif
用法
#define num 7
#include "fc.h"
int array[sum][num];
您可以手动将类似内容放入宏中。
您只需要进行几次迭代,因为阶乘增长如此之快,而支持的最大整数通常只有 64 位宽。(x) * (((x)-1)>0?((x)-1):1) * (((x)-2)>0?((x)-2):1) ...
虽然如上所示的表达式可能看起来很复杂,但对于整数常量表达式
(比如 , , ),保证生成一个整数常量表达式,即 something
适用于初始化静态数组大小、位域大小和大小写标签)。
此外,对于预处理器识别的整数参数(例如,、、),表达式也是预处理器可识别的,
即,适合作为预处理器条件的参数。x
1
1+2
sizeof(0)*3
1
42u
0x1000ull
#if
那么如何才能得到这样的宏呢?
首先,您需要一个阶乘参数的上限,该参数不会溢出无符号的 long long(最大保证为 由预处理器和 C 编译器支持,typicall 64 位宽)。
你可以得到类似的东西
#include <stdio.h>
unsigned long long factorial(unsigned long long X){ if(X<=1) return 1; return X*factorial(X-1); }
int main(){
int i=2;
for(; i<100 && factorial(i-1)<factorial(i); i++){ if(0) printf("%016llx \n", factorial(i)); }
printf("%d\n", i-1); //22
}
对于 64 位无符号多头,它是 22。
知道它是 22,您可以生成宏:
printf("#define FACTORIAL(X) ((X)>22 || (X)<0 ? 0 : (1 ");
for(int i=0; i<22; i++) printf(" * ((int)+(X)-%d > 0 ? (X)-%dULL : 1)", i, i);
printf("))\n");
以上印刷品
#define FACTORIAL(X) ((X)>22 ? 0 : (1 * ((int)+(X)-0 > 0 ? (X)-0ULL : 1) * ((int)+(X)-1 > 0 ? (X)-1ULL : 1) * ((int)+(X)-2 > 0 ? (X)-2ULL : 1) * ((int)+(X)-3 > 0 ? (X)-3ULL : 1) * ((int)+(X)-4 > 0 ? (X)-4ULL : 1) * ((int)+(X)-5 > 0 ? (X)-5ULL : 1) * ((int)+(X)-6 > 0 ? (X)-6ULL : 1) * ((int)+(X)-7 > 0 ? (X)-7ULL : 1) * ((int)+(X)-8 > 0 ? (X)-8ULL : 1) * ((int)+(X)-9 > 0 ? (X)-9ULL : 1) * ((int)+(X)-10 > 0 ? (X)-10ULL : 1) * ((int)+(X)-11 > 0 ? (X)-11ULL : 1) * ((int)+(X)-12 > 0 ? (X)-12ULL : 1) * ((int)+(X)-13 > 0 ? (X)-13ULL : 1) * ((int)+(X)-14 > 0 ? (X)-14ULL : 1) * ((int)+(X)-15 > 0 ? (X)-15ULL : 1) * ((int)+(X)-16 > 0 ? (X)-16ULL : 1) * ((int)+(X)-17 > 0 ? (X)-17ULL : 1) * ((int)+(X)-18 > 0 ? (X)-18ULL : 1) * ((int)+(X)-19 > 0 ? (X)-19ULL : 1) * ((int)+(X)-20 > 0 ? (X)-20ULL : 1) * ((int)+(X)-21 > 0 ? (X)-21ULL : 1)))
您可以测试此宏是否具有预处理器识别的整数:
#if FACTORIAL(1)!=1 || FACTORIAL(6)!=720 || FACTORIAL(22) != 0xeea4c2b3e0d80000
#error ""
#endif
对于未被预处理器识别的整数常量表达式:
_Static_assert(FACTORIAL(6*sizeof(char))==720,"");
评论
FACTORIAL()
x++
我通常会编写一个简短的 shell 脚本来预先计算三元表达式的值。
// $ for i in $(seq 9); do echo "i == $i ? $(($(seq $i|paste -sd'*'))) : \\"; done
#define FACTORIAL(i) ( \
i == 1 ? 1 : \
i == 2 ? 2 : \
i == 3 ? 6 : \
i == 4 ? 24 : \
i == 5 ? 120 : \
i == 6 ? 720 : \
i == 7 ? 5040 : \
i == 8 ? 40320 : \
i == 9 ? 362880 : \
-1)
int array[FACTORIAL(3)][3];
这是可能的。正如其他人所指出的,您可以对值进行硬编码。话虽如此,您也可以采用计算方法。如果你对编译时元编程感兴趣,我建议你看看 Order-pp。使用该库,您可以轻松地实现阶乘函数,如下所示:
#define ORDER_PP_DEF_8fac ORDER_PP_FN \
(8fn (8N \
,8if (8is_0 (8N) \
,1 \
,8mul (8N, 8fac (8dec (8N))) \
) \
) \
)
#define FACTORIAL(n) ORDER_PP (8to_lit (8fac (n)))
FACTORIAL (15) // 15! == 1307674368000
但是,如果您想要一种更简约的方法,gcc 中有一个漏洞可以启用宏递归。它适用于 gcc 11 及更早版本。它还使用了许多 gcc 扩展,但由于该漏洞在其他任何地方都不起作用,因此没有问题:
#2""3 // disables warnings
// macro revival exploit
#define PRAGMA(p...) _Pragma(#p)
#define REVIVE(m) PRAGMA(push_macro(#m)) PRAGMA(pop_macro(#m))
// deferred macro application
#define FX(f,x) REVIVE(FX) f x
// Church booleans
#define IF_(t,f...) REVIVE(IF_) t
#define IF_1(t,f...) REVIVE(IF_1) f
// is-zero predicate
#define ZP(...) IF_##__VA_OPT__(1)
// base 1 comma encoded arithmetic
#define DEC(n,ns...) REVIVE(DEC) (ns)
#define INC(ns...) REVIVE(INC) (,ns)
// acc is used internally by FACT and should not be given an argument by the user
#define FACT(n,acc...) REVIVE(FACT) (ZP n (IF_##__VA_OPT__(1) (1ul, (acc)), __VA_OPT__((acc) *) FX (FACT, (DEC n, acc + 1ul))))
int main () {printf ("%llu", FACT((,,,,, ,,,,, ,,,,,)));} // 15! == 1307674368000
如果选择后者,这里有一个奖励函数,可以将逗号编码的数字转换为 C 整数表达式:
#define TO_LIT(n...) REVIVE(TO_LIT) ZP(n) (0, 1 + TO_LIT DEC(n))
TO_LIT (,,,,) // (1+ (1+ (1+ (1+ (0))))) == 4
如果要测试对此的修改,请使用选项 -E 和 -P 获取预处理器输出。
评论
(x)*((x-1)>0?(x-1):1)*((x-2)>0?(x-2):1)...