什么是堆栈溢出,如何解决?

3
这个程序的目的是通过将其与前11个质数整数进行除法测试来确定介于1和1000之间的数字是否为质数。该程序在大多数输入情况下都能正常运行。但是,当我输入像468这样的整数时,会检测到栈溢出错误。什么是栈溢出,如何解决这个问题?
我已经尝试研究了栈溢出,但找不到与我的程序相关的具体示例。由于我相对新于使用C进行编程,因此我不知道可以尝试哪些替代方法来修改程序。
char divStatement[] = " is divisible by ";

if ((userInput % 31) == 0) {
    div31 = true;
    strcat(divStatement, "31, ");
}

if (div2 || div3 || div5 || div7 || div11 || div13 || div17 || div19 || div23 || div29 || div31) {
        divStatement[strlen(divStatement) - 2] = '.';
        divStatement[strlen(divStatement) - 1] = '\n';
        printf("%d%s", userInput, divStatement);
        printf("%d is not prime.\n", userInput);
}
else {
        printf("%d is prime.\n", userInput);
}

输出实际上能够正常工作。然而,在程序结束时,终端会输出:

***stack smashing detected ***: ./a.out terminated 
Aborted

这将会比把大量不合理的变量混在一起要好得多,最好使用数组。 - tadman
5
不相关,但这段代码需要大量重构。 - Eugene Sh.
很确定你的 strcat() 函数试图修改只读内存,而且可能会超出数组的末尾(如果它能工作的话,那么这个数组在堆栈上)。请了解一下 strcat() 的工作原理(然后在可写内存中为字符串分配更多空间)。 - Michael Dorgan
当询问关于运行时问题的问题时,就像这个问题一样,请发布一个[mcve],显示输入、实际输出和预期输出。注意:发布的代码应该可以干净地编译,而不是从某些代码中间取出的片段。 - user3629249
4个回答

10
char divStatement[] = " is divisible by ";

if ((userInput % 31) == 0) {
    div31 = true;
    strcat(divStatement, "31, ");
}

divStatement是一个字符数组,恰好足够容纳" is divisible by "和一个\0终止符。你不能将"31, "追加到它上面,因为它没有额外的松弛空间。

一个简单的解决方法是给这个数组一个明确的长度,以便处理程序可能追加的任何内容。

char divStatement[1000] = " is divisible by ";

1
这个答案在技术上是正确的(最好的正确方式 :-)。它回答了帖子中关于“如何修复堆栈破坏”的问题。但它未能回答发帖者真正应该问的问题:“如何在不先构建完整字符串的情况下打印内容。以及如何使用循环和数组”。 - HAL9000

2
由于编译器使用的内存保护机制,导致返回堆栈破坏错误。您可以禁用此功能,但这会导致被所谓的堆栈缓冲区溢出攻击利用的漏洞(请参见此计算机文件在YouTube上的视频)。
您没有使用特定大小声明字符数组,但因为您正在使用字符串文字进行初始化,所以divStatement字符数组的大小设置为18个字节(17个用于" is divisible by "中的字符和1个用于称为null character的字符串终止符"\0")。根据C99标准6.4.5/5“字符串文字-语义”(如此问题的答案所述)。
通过多字节字符序列初始化静态存储期数组,其长度正好足以容纳该序列。粗略地说,编译器知道应该为此特定字符数组保留仅18个字节的内存,因此写入数组不应修改其第18个字节之后的数据(出于安全考虑)。当您连接字符串时,实际上正在尝试写入超过18个字节,这会触发安全机制,从而导致错误。由于您只测试1到1000之间的数字,并且仅使用前11个质数,因此您知道质数最多只有3个字符长。加上", "这5个额外字符,总共是18+5=23个字符。因此,您可以将divStatement显式声明为大小为23的字符数组,即 divStatement[23] = " is divisible by "。至于获取有关代码的更一般性反馈,您应该查看代码审核社区

1

这不是你问题的答案,而是你应该问的问题的部分答案。我太累了,无法给出适当的解释。但是你需要在代码中某个地方加入类似以下内容:

if ((userInput % n) == 0)
  {
    printf("%d is divisible by %d\n", userInput, n);
    is_prime == 0;
  }

拥有所有11个最初的质数数组也是一个好主意:
const int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};

我不会破坏所有7个乐趣,告诉你如何将此包装在循环中,以及如何声明/初始化nis_prime。玩得开心做作业。希望截止日期不要太快:-)


1
以下是发布在https://www.techopedia.com/definition/16157/stack-smashing上的栈溢出定义:
定义 - 什么是栈溢出?
*栈溢出是一种漏洞形式,可以强制计算机应用程序或操作系统的堆栈溢出。这可能导致程序/系统被破坏并崩溃。
堆栈是一种先进后出电路,是一种缓冲区,其中包含操作的中间结果。简而言之,栈溢出是将更多数据放入堆栈中超过其容量的行为。技术娴熟的黑客可以有意地向堆栈中引入过多的数据。过多的数据可能存储在其他堆栈变量中,包括函数返回地址。当函数返回时,它会跳转到堆栈上的恶意代码,这可能会破坏整个系统。堆栈上的相邻数据受到影响并迫使程序崩溃。*
char divStatement[] = " is divisible by ";

并且

strcat(divStatement, "31, ");

调用strcat()的目的是将字符串"31, "附加到一个只能容纳字符串"is divisible by "的数组中。结果是数组溢出。这种溢出行为是未定义的。在这种情况下,它破坏了堆栈,可能就在某个堆栈帧或其他链接处。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接