是否可以在 C 语言中编译时计算处理器值的阶乘值?

Is it possible to compute factorial value of a proprocessor value during compile time in C?

提问人:Cinverse 提问时间:2/13/2023 更新时间:5/9/2023 访问量:180

问:

#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];

我想全局定义,但它的大小取决于预处理器的值。因此,我希望在编译时确定值(阶乘)。arraynumnum

可能吗?如果是,如何?

c-preprocessor 预处理器指令

评论

0赞 Some programmer dude 2/13/2023
C 支持可变长度数组,不需要宏和数组大小的编译时计算。除非您对您的任务有非常具体的要求或限制(您没有告诉我们)?
1赞 Petr Skocik 2/13/2023
手动将类似的东西放入宏中。您只需要进行几次迭代,因为阶乘增长如此之快,而支持的最大整数通常只有 64 位宽。(x)*((x-1)>0?(x-1):1)*((x-2)>0?(x-2):1)...
0赞 0___________ 2/13/2023
@PSkocik,但在预处理过程中不会完成
0赞 Petr Skocik 2/14/2023
@0___________如果 X 是,它将是一个整数常量表达式。
0赞 0___________ 2/14/2023
@PSkocik 在预处理期间,编译器可能会对其进行优化。但是预处理器对 C 表达式和 ststements 一无所知

答:

3赞 0___________ 2/13/2023 #1

在单独的 .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];
2赞 Petr Skocik 2/14/2023 #2

您可以手动将类似内容放入宏中。 您只需要进行几次迭代,因为阶乘增长如此之快,而支持的最大整数通常只有 64 位宽。(x) * (((x)-1)>0?((x)-1):1) * (((x)-2)>0?((x)-2):1) ...

虽然如上所示的表达式可能看起来很复杂,但对于整数常量表达式 (比如 , , ),保证生成一个整数常量表达式,即 something 适用于初始化静态数组大小、位域大小和大小写标签)。 此外,对于预处理器识别的整数参数(例如,、、),表达式也是预处理器可识别的, 即,适合作为预处理器条件的参数。x11+2sizeof(0)*3142u0x1000ull#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,"");

评论

1赞 John Bollinger 2/14/2023
我可能倾向于使用单个术语的辅助宏来编写该宏。当然,无论哪种方式,问题在于沿着这些思路的宏多次评估其参数,这可能会产生令人惊讶和不需要的结果(例如,如果参数是 )。FACTORIAL()x++
0赞 Petr Skocik 2/14/2023
@JohnBollinger 可以将上述方法 + _Generic + 内联函数(语句表达式会更好,恕我直言,如果只有编译器形成:gcc.gnu.org/bugzilla/show_bug.cgi?id=93239)来创建一个既保留整数常量又防止多次计算的宏,但我不想用它来膨胀答案。朴素宏的这个缺陷是众所周知的,每当有全大写的标识符时,就会出现这种情况。
3赞 KamilCuk 2/14/2023 #3

我通常会编写一个简短的 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];
0赞 AnyMagicalChickenWillDo 5/9/2023 #4

这是可能的。正如其他人所指出的,您可以对值进行硬编码。话虽如此,您也可以采用计算方法。如果你对编译时元编程感兴趣,我建议你看看 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 获取预处理器输出。