组合函数的 c 程序中的分段错误(核心转储)错误
segmentation fault (core dumped) error in a c program for combination function
#include <stdio.h>
#include <stdlib.h>
int factorial(int i) {
if(i == 1) {
return 1;
}
else {
return i*factorial(i - 1);
}
}
int combination(int l, int m) {
return factorial(l)/(factorial(l-m)*factorial(m));
}
int main() {
int n,r;
printf("Input taken in form of nCr\n");
printf("Enter n: ");
scanf("%d", &n);
printf("Enter r: ");
scanf("%d", &r);
int y = combination(n, r);
printf("Result: %d", y);
return 0;
}
试图编写一个简单的代码来计算数学中的组合函数。它适用于小值,基本上工作到 n = 12,并且从 n = 13 开始给出错误的值。
同样对于 n = 15 和 r = 2,它 returns 结果 -4。
它给出了错误
segmentation fault (core dumped)
对于 n = 40 和 r = 20。
我想知道如何解决这个问题以及为什么会这样。
当我 运行 你的程序在调试器中使用 n = 40
和 r = 20
在一个用 Microsoft Visual Studio 编译的 32 位二进制文件上时,我没有得到分段错误,但我在以下行中得到除以零的错误:
return factorial(l)/(factorial(l-m)*factorial(m));
factorial(l-m)
和 factorial(m)
都计算为 factorial(20)
,即 2,192,834,560
.
假设sizeof(int) == 4
(32位),这个数不能用带符号的int
表示。因此,int
溢出,根据官方 C 标准,导致 undefined behavior.
然而,即使行为未定义,我也可以合理地推测会发生以下情况:
由于溢出,数字2,192,834,560
将变为-2,102,132,736
。这是因为第二个数字对应于Two's complement binary representation中的第一个数字。
由于这个数字在您的代码中与自身相乘(假设 n = 40
和 r = 20
),那么相乘的结果将是 4,418,962,039,762,845,696
。这个数字肯定放不进有符号的int
,所以又出现溢出
这个数字的十六进制表示是0x3D534E9000000000
。
由于这个大数放不进32位的整数,所以多余的位全部去掉,相当于对结果进行modulo UINT_MAX + 1
(4,294,967,296
).这个模运算的结果是 0
.
因此,表达式
factorial(l-m)*factorial(m)
计算为 0
。
这意味着行
return factorial(l)/(factorial(l-m)*factorial(m));
会导致被零除异常。
解决处理大数问题的一种方法是使用浮点数而不是整数。这些可以处理非常大的数字而不会溢出,但您可能会失去精度。如果你使用 double
而不是 float
,你将不会那么容易丢失精度,即使你这样做,精度损失也会更小。
我猜有 2 个影响相互作用:
- 您的整数溢出,即
factorial(i)
的值对于足够大的 i
将变为负数,从而导致
- 您的递归(
factorial
调用自身)占用了您所有的堆栈 space。
尝试将 factorial
中的条件从 if(i == 1)
更改为:
int factorial(int i) {
if(1 == i) {
return 1;
} else if(1 > i) {
return -1;
}
return i * factorial(i - 1);
}
这应该可以让您摆脱 SEGFAULT。
对于整数溢出,唯一可能的解决方案是不依赖于 C 整数运算,而是使用一些 bignum 库(或自己编写代码)。
对可能发生的事情的一些解释:
正如@WhozCraig 所指出的,整数最多只能保留 INT_MAX
范围内的数字。然而,factorial(i)
即使是相对较小的数字也会爆炸。
但是,C 不会捕获此异常,您的整数将悄无声息地溢出为负数。
这意味着在某些时候你开始用负数喂 factorial
。
然而,对于每个函数调用,一些数据必须被压入堆栈(通常是 return 地址和局部变量,可能包括函数参数)。
此内存将在函数 returns 后释放。
这意味着,如果您调用 factorial(40)
,如果一切正常,您将消耗 1 次调用 factorial
.
的内存量的 40 倍
由于你的factorial
没有正确处理负数,它最终会无休止地调用自己,时不时溢出,直到随机命中条件i == 1
。
从表面上看,在大多数情况下,这不会在您的堆栈耗尽之前发生。
这里是溢出问题。您的结果高于最大 int 值。
13! = 6227020800
超过 INT_MAX
(2147483647)。如果你想处理更大的数字,你应该使用其他变量类型(例如 unsigned long long
),或者在你的函数中处理溢出以避免内存崩溃。
这里有一个关于 c 语言溢出检查的有趣话题 here。
Also for n = 15 and r = 2, it returns the result -4
当一个变量上溢时,它可以下溢和
循环溢出。这就是你得到负值的原因。我不确定,但我认为这是相关的。如果有人可以验证这一点,那就太好了。
13的价值!是 6227020800,它太大而不适合 32 位整数。通过尝试计算这个阶乘或更大的结果导致溢出 32 位 int
。有符号整数溢出调用 undefined behavior.
在某些情况下,这种未定义的行为表现为输出错误的值,而在其他情况下则表现为崩溃。 factorial
函数崩溃的情况很可能是传递了一个小于 1 的值,这意味着递归调用将尝试一直向下到 INT_MIN
但在此之前会填满堆栈.
即使更改为 long long
也不足以解决此问题,因为中间结果会溢出。那么你如何解决这个问题?如果您手动计算这些值,则不会将所有数字相乘然后除以两个大数字。你会写出这些因素并抵消等式顶部和底部的项。例如,假设您要计算 12C7。你会这样写:
12 * 11 * 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1
------------------------------------------------
( 5 * 4 * 3 * 2 * 1 ) * (7 * 6 * 5 * 4 * 3 * 2 * 1)
那就抵消掉7!从上到下:
12 * 11 * 10 * 9 * 8
---------------------
5 * 4 * 3 * 2
然后取消其他条款:
12 * 11 * 10 * 9 * 8 12 * 11 * 2 * 9 * 8 12 * 11 * 2 * 9
--------------------- = -------------------- = --------------- = 4 * 11 * 2 * 9
5 * 4 * 3 * 2 4 * 3 * 2 3
然后乘以剩下的:
4 * 11 * 2 * 9 = 792
现在在代码中执行此操作。 :) 请务必将所有数据类型更改为 long long
,因为 40C20 的结果仍然比32 位 int
可以容纳什么。此类型保证至少为 64 位。
#include <stdio.h>
#include <stdlib.h>
int factorial(int i) {
if(i == 1) {
return 1;
}
else {
return i*factorial(i - 1);
}
}
int combination(int l, int m) {
return factorial(l)/(factorial(l-m)*factorial(m));
}
int main() {
int n,r;
printf("Input taken in form of nCr\n");
printf("Enter n: ");
scanf("%d", &n);
printf("Enter r: ");
scanf("%d", &r);
int y = combination(n, r);
printf("Result: %d", y);
return 0;
}
试图编写一个简单的代码来计算数学中的组合函数。它适用于小值,基本上工作到 n = 12,并且从 n = 13 开始给出错误的值。 同样对于 n = 15 和 r = 2,它 returns 结果 -4。 它给出了错误
segmentation fault (core dumped)
对于 n = 40 和 r = 20。 我想知道如何解决这个问题以及为什么会这样。
当我 运行 你的程序在调试器中使用 n = 40
和 r = 20
在一个用 Microsoft Visual Studio 编译的 32 位二进制文件上时,我没有得到分段错误,但我在以下行中得到除以零的错误:
return factorial(l)/(factorial(l-m)*factorial(m));
factorial(l-m)
和 factorial(m)
都计算为 factorial(20)
,即 2,192,834,560
.
假设sizeof(int) == 4
(32位),这个数不能用带符号的int
表示。因此,int
溢出,根据官方 C 标准,导致 undefined behavior.
然而,即使行为未定义,我也可以合理地推测会发生以下情况:
由于溢出,数字2,192,834,560
将变为-2,102,132,736
。这是因为第二个数字对应于Two's complement binary representation中的第一个数字。
由于这个数字在您的代码中与自身相乘(假设 n = 40
和 r = 20
),那么相乘的结果将是 4,418,962,039,762,845,696
。这个数字肯定放不进有符号的int
,所以又出现溢出
这个数字的十六进制表示是0x3D534E9000000000
。
由于这个大数放不进32位的整数,所以多余的位全部去掉,相当于对结果进行modulo UINT_MAX + 1
(4,294,967,296
).这个模运算的结果是 0
.
因此,表达式
factorial(l-m)*factorial(m)
计算为 0
。
这意味着行
return factorial(l)/(factorial(l-m)*factorial(m));
会导致被零除异常。
解决处理大数问题的一种方法是使用浮点数而不是整数。这些可以处理非常大的数字而不会溢出,但您可能会失去精度。如果你使用 double
而不是 float
,你将不会那么容易丢失精度,即使你这样做,精度损失也会更小。
我猜有 2 个影响相互作用:
- 您的整数溢出,即
factorial(i)
的值对于足够大的i
将变为负数,从而导致 - 您的递归(
factorial
调用自身)占用了您所有的堆栈 space。
尝试将 factorial
中的条件从 if(i == 1)
更改为:
int factorial(int i) {
if(1 == i) {
return 1;
} else if(1 > i) {
return -1;
}
return i * factorial(i - 1);
}
这应该可以让您摆脱 SEGFAULT。
对于整数溢出,唯一可能的解决方案是不依赖于 C 整数运算,而是使用一些 bignum 库(或自己编写代码)。
对可能发生的事情的一些解释:
正如@WhozCraig 所指出的,整数最多只能保留 INT_MAX
范围内的数字。然而,factorial(i)
即使是相对较小的数字也会爆炸。
但是,C 不会捕获此异常,您的整数将悄无声息地溢出为负数。
这意味着在某些时候你开始用负数喂 factorial
。
然而,对于每个函数调用,一些数据必须被压入堆栈(通常是 return 地址和局部变量,可能包括函数参数)。
此内存将在函数 returns 后释放。
这意味着,如果您调用 factorial(40)
,如果一切正常,您将消耗 1 次调用 factorial
.
由于你的factorial
没有正确处理负数,它最终会无休止地调用自己,时不时溢出,直到随机命中条件i == 1
。
从表面上看,在大多数情况下,这不会在您的堆栈耗尽之前发生。
这里是溢出问题。您的结果高于最大 int 值。
13! = 6227020800
超过 INT_MAX
(2147483647)。如果你想处理更大的数字,你应该使用其他变量类型(例如 unsigned long long
),或者在你的函数中处理溢出以避免内存崩溃。
这里有一个关于 c 语言溢出检查的有趣话题 here。
Also for n = 15 and r = 2, it returns the result -4
当一个变量上溢时,它可以下溢和 循环溢出。这就是你得到负值的原因。我不确定,但我认为这是相关的。如果有人可以验证这一点,那就太好了。
13的价值!是 6227020800,它太大而不适合 32 位整数。通过尝试计算这个阶乘或更大的结果导致溢出 32 位 int
。有符号整数溢出调用 undefined behavior.
在某些情况下,这种未定义的行为表现为输出错误的值,而在其他情况下则表现为崩溃。 factorial
函数崩溃的情况很可能是传递了一个小于 1 的值,这意味着递归调用将尝试一直向下到 INT_MIN
但在此之前会填满堆栈.
即使更改为 long long
也不足以解决此问题,因为中间结果会溢出。那么你如何解决这个问题?如果您手动计算这些值,则不会将所有数字相乘然后除以两个大数字。你会写出这些因素并抵消等式顶部和底部的项。例如,假设您要计算 12C7。你会这样写:
12 * 11 * 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1
------------------------------------------------
( 5 * 4 * 3 * 2 * 1 ) * (7 * 6 * 5 * 4 * 3 * 2 * 1)
那就抵消掉7!从上到下:
12 * 11 * 10 * 9 * 8
---------------------
5 * 4 * 3 * 2
然后取消其他条款:
12 * 11 * 10 * 9 * 8 12 * 11 * 2 * 9 * 8 12 * 11 * 2 * 9
--------------------- = -------------------- = --------------- = 4 * 11 * 2 * 9
5 * 4 * 3 * 2 4 * 3 * 2 3
然后乘以剩下的:
4 * 11 * 2 * 9 = 792
现在在代码中执行此操作。 :) 请务必将所有数据类型更改为 long long
,因为 40C20 的结果仍然比32 位 int
可以容纳什么。此类型保证至少为 64 位。