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

时间:2013-08-01 00:59:55

标签: c memory-management

在处理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)?内存中的这些字符串(堆栈或堆)在哪里?

感谢您的帮助。

6 个答案:

答案 0 :(得分:7)

  

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

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

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

  

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

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

  

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

由于您已在char []内将其声明为main(),因此它们是具有automatic storage的数组,出于实际目的,它们意味着它们位于堆栈中。

答案 1 :(得分:2)

修改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"(带有空终止符)。

它们都是一个接一个地定位(由于你编写它们的顺序很简单)形成一个你可以写入的内存块(但不应该!)......这就是为什么没有seg fault,你可以通过在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 <binary> > <text file>.txt命令或objdump -t <your_binary> > <text file>.sym命令转储程序中的所有符号。这些命令还应该为您提供每个符号所在的部分

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

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

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

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

答案 2 :(得分:1)

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

答案 3 :(得分:0)

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));
}

答案 4 :(得分:0)

以下是您的程序未崩溃的原因:

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

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

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

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

答案 5 :(得分:0)

嗯......由于堆只用于动态分配内存和东西,所以字符串都在堆栈中。

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

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

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

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