在没有明显内存违规的情况下中止而不是段错误

在处理C字符串时,我遇到了这种奇怪的行为。 这是K&R书中的一个练习,我应该编写一个函数,将一个字符串附加到另一个字符串的末尾。 这显然要求目标字符串分配足够的内存,以便源字符串适合。 这是代码:

/* strcat: Copies contents of source at the end of dest */ char *strcat(char *dest, const char* source) { char *d = dest; // Move to the end of dest while (*dest != '\0') { dest++; } // *dest is now '\0' while (*source != '\0') { *dest++ = *source++; } *dest = '\0'; return d; } 

在测试期间,我编写了以下内容,期望在程序运行时发生段错误:

 int main() { char s1[] = "hello"; char s2[] = "eheheheheheh"; printf("%s\n", strcat(s1, s2)); } 

据我所知,s1获得了一个由6个chars分配的数组,s2是一个由13个chars的数组。 我认为当strcat尝试在索引高于6的情况下写入s1时,程序会出现段错误。 相反,一切正常,但程序不会干净地退出,而是:

 helloeheheheheheh zsh: abort ./a.out 

并退出代码134,我认为这只是意味着中止。

为什么我没有得到段错误(如果在堆栈上分配字符串,则覆盖s2)? 内存中的这些字符串(堆栈或堆)在哪里?

谢谢你的帮助。

我认为当strcat尝试在索引高于6的情况下写入s1时,程序会出现段错误。

在堆栈上分配的内存范围之外写入是未定义的行为 。 通常(但不总是)调用此未定义的行为会导致段错误。 但是,您无法确定是否会发生段错误。

维基百科链接很好地解释了它:

当发生未定义行为的实例时,就语言规范而言,任何事情都可能发生,也许什么都没发生。

因此,在这种情况下,您可能会遇到段错误,程序可能会中止,或者有时可能会运行正常。 或者,任何事情。 没有办法保证结果。

内存中的这些字符串(堆栈或堆)在哪里?

因为你在main()声明它们是char [] ,所以它们是具有自动存储的数组,出于实际目的,它们意味着它们在堆栈中。

编辑1:

我将尝试解释你如何为自己找到答案。 我不确定实际发生了什么,因为这不是定义的行为(正如其他人所说),但你可以做一些简单的调试来弄清楚你的编译器实际上在做什么。

原始答案

我的猜测是他们都在堆栈中。 您可以通过以下方式修改代码来检查:

 int main() { char c1 = 'X'; char s1[] = "hello"; char s2[] = "eheheheheheh"; char c2 = '3'; printf("%s\n", strcat(s1, s2)); } 

c1c2将在堆栈中。 知道你可以检查s1s2是否也是如此。

如果c1的地址小于s1s1的地址小于c2则它在堆栈上。 否则它可能在你的.bss部分(这将是聪明的事情,但会破坏递归)。

我依赖堆栈上的字符串的原因是,如果你在函数中修改它们,并且该函数调用它自己,那么第二个调用将没有自己的字符串副本,因此无效。 ..然而,编译器仍然知道这个函数不是递归的,并且可以将字符串放在.bss所以我可能是错的。

假设我的猜测它在堆栈上是正确的,在你的代码中

 int main() { char s1[] = "hello"; char s2[] = "eheheheheheh"; printf("%s\n", strcat(s1, s2)); } 

"hello" (使用null终止符)被压入堆栈,然后是"eheheheheheh" (使用null终止符)。

它们都是一个接一个地定位(由于你编写它们的顺序很简单)形成一个你可以写入的内存块(但不应该!)……这就是为什么没有seg错误,你可以通过打破printf并查看地址来看到这一点。

如果我是对的, s2 == (uintptr_t)s1 + (strlen(s1) + 1)应该是真的。

使用修改代码

 int main() { char s1[] = "hello"; char c = '3'; char s2[] = "eheheheheheh"; printf("%s\n", strcat(s1, s2)); } 

如果我是对的,应该看c被覆盖……

但是,如果我错了并且它在.bss部分,那么它们仍然可以相邻,你将覆盖它们而没有seg故障。

如果您真的想知道,请将其拆解:

不幸的是我只知道如何在Linux上做到这一点。 尝试使用nm > .txt命令或objdump -t > .sym命令转储程序中的所有符号 。 这些命令还应该为您提供每个符号所在的部分

在文件中搜索s1s2符号,如果找不到它们应该意味着它们在堆栈中,但我们将在下一步中检查它。

使用objdump -S your_binary > text_file.S命令(确保使用调试符号构建二进制文件),然后在文本编辑器中打开.S文件。

再次搜索s1s2符号,(希望没有任何其他符号,我怀疑不是,但我不确定)。

如果您发现其定义后跟pushsub %esp命令,那么它们就在堆栈中。 如果您不确定他们的定义是什么意思,请将其发回此处,让我们一起来看看。

没有seg故障甚至是覆盖,因为它可以使用第二个字符串的内存并仍然起作用。 甚至给出正确的答案。 中止是程序意识到出错的一个标志。 尝试颠倒声明字符串的顺序,然后重试。 它可能不会那么令人愉快。

 int main() { char s1[] = "hello"; char s2[] = "eheheheheheh"; printf("%s\n", strcat(s1, s2)); } 

改为使用:

  int main() { char s1[20] = "hello"; char s2[] = "eheheheheheh"; printf("%s\n", strcat(s1, s2)); } 

这就是你的程序没有崩溃的原因:

您的字符串声明为数组(s1 []和s2 [])。 所以他们在堆栈上。 恰好在s1 []之后,s2 []的内存正好发生了。 因此,当调用strcat()时,它所做的就是将s2 []中的每个字符向前移动一个字节。 堆栈作为堆栈是可读写的。 所以你做的事情没有限制。

但我相信编译器可以自由地找到s1 []和s2 [],它看起来很合适,所以这只是一个快乐的事故。

现在让你的程序崩溃相对容易

  1. 在你的调用中交换s1和s2:而不是strcat(s1,s2),执行strcat(s2,s1)。 这应该导致堆栈粉碎exception。
  2. 将s1 []和s2 []更改为* s1和* s2。 当您写入只读段时,这应该导致段错误。

嗯….字符串都在堆栈中,因为堆只用于动态分配内存和东西..

segfault用于无效的内存访问,但是对于这个数组,你只是在写一些超出边界(在边界之外)的数组,所以写作时我不认为你会有问题….因为在C中它实际上留给了程序员,以确保事物保持在arrays的范围内。

同时阅读时如果你使用指针 – 我不认为会有问题,因为你可以继续阅读,直到你想要的地方,并使用以前的长度之和。 但是如果你使用string.h中提到的函数,它们会在存在空字符“\ 0”的情况下进行中继,以决定停止操作的位置 – 因此我认为你的函数有效!

但终止也可能表明任何其他变量/某些东西可能已经存在于字符串位置旁边可能已经用char值写了….访问那些可能导致程序退出!!

希望这有帮助….顺便说一句好问题!