递归实现阶乘的栈溢出风险在于调用深度等于n,每层递归需压入返回地址、局部变量等,当n超过几百(如1000)时易耗尽默认1–8MB栈空间,引发Segmentation fault或stack overflow。

递归实现阶乘时栈溢出风险在哪
递归写 factorial(n) 看似简洁,但实际调用深度等于 n。当 n 超过几百(比如 n = 1000),多数编译器默认栈空间(通常 1–8 MB)就会被压垮,触发 Segmentation fault 或 stack overflow 异常。
关键不是“能不能算”,而是“谁来承担调用开销”。每层递归都得压入返回地址、局部变量、寄存器备份——这些加起来远超一个 int 占用。
- 不建议在生产代码中对未知大小的
n使用纯递归 - 若必须用递归,应加守卫:
if (n > 1000) throw std::runtime_error("n too large for recursion"); - 尾递归优化(如 GCC 的
-O2)对简单阶乘无效,因为return n * factorial(n-1)不是尾调用(乘法在 return 前)
循环实现阶乘要注意整数溢出
用 for 循环算阶乘更可控,但 int 或 long long 很快就爆掉:13! = 6227020800 已超出 int(通常 32 位),21! 就超过 long long(64 位)最大值 9223372036854775807。
这意味着:即使你写对了循环逻辑,结果也大概率是错的——而且不会报错,只会静默溢出(未定义行为)。
立即学习“C++免费学习笔记(深入)”;
- 务必根据预期输入范围选类型:小值用
unsigned long long,大值必须上boost::multiprecision::cpp_int或自己实现大数 - 可在循环中加入溢出检测,例如用
__builtin_mul_overflow(GCC/Clang)或手动比较:if (result > ULLONG_MAX / i) throw std::overflow_error("factorial overflow"); -
0!和1!必须显式处理为1,否则循环从i=2开始时会漏掉
模板 + constexpr 实现编译期阶乘
如果参数是字面量且不大(比如用于数组长度、模板参数),用 constexpr 递归模板能彻底避免运行时开销,并在编译时报错溢出。
templateconstexpr unsigned long long factorial() { static_assert(N < 21, "N too large for constexpr factorial"); return (N <= 1) ? 1 : N * factorial (); }
调用如 constexpr auto f5 = factorial();,编译器直接替换为 120;若写 factorial(),编译失败并提示 static_assert 错误。
- 不能用于运行时变量:
int n; std::cin >> n; factorial是非法的(); -
static_assert检查的是模板实例化时的N,不是运行时值,所以安全可靠 - 比宏更类型安全,比普通函数更早暴露问题
什么时候该用第三方大数库
当需求明确要支持 n > 20 且结果需精确(比如密码学、组合数学验证),硬编码 unsigned long long 就是自欺欺人。此时应切换到真正能处理任意精度的方案。
最轻量的选择是 boost::multiprecision::cpp_int,它头文件即用,无链接依赖:
#includeusing namespace boost::multiprecision; cpp_int factorial(int n) { cpp_int result = 1; for (int i = 2; i <= n; ++i) result *= i; return result; }
- 不要试图自己手写“字符串模拟乘法”——边界多、易错、维护成本高
- 注意
cpp_int运算比原生类型慢几个数量级,只在必要时启用 - 若项目已用 C++23,可关注
和未来可能的std::bigint(尚未标准化)
真正麻烦的从来不是“怎么写阶乘”,而是想清楚:你要的是编译期常量、运行时小值快速计算,还是任意精度下的正确性——选错路径,后面全是补丁。










