C 中止,而不是清除内存冲突的segfault

C 中止,而不是清除内存冲突的segfault,c,memory-management,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 d

我在处理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;
}
在测试期间,我编写了以下代码,希望在程序运行时发生segfault:

int main() {
  char s1[] = "hello";
  char s2[] = "eheheheheheh"; 
  printf("%s\n", strcat(s1, s2));
}
据我所知,s1分配了6个
字符的数组,s2分配了13个
字符的数组。我认为当strcat试图以高于6的索引写入s1时,程序会出错。相反,一切正常,但程序不会干净地退出,而是:

helloeheheheheheh
zsh: abort      ./a.out
然后以代码134退出,我想这意味着中止

为什么我没有得到segfault(或者如果在堆栈上分配了字符串,则覆盖s2)?这些字符串在内存中的什么位置(堆栈或堆)

谢谢你的帮助

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

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

我认为当strcat试图以高于
6
的索引写入
s1
时,程序会出错

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

维基百科链接很好地解释了这一点:

当一个未定义行为的实例发生时,就语言规范而言,任何事情都可能发生,可能什么都不会发生

因此,在这种情况下,您可能会遇到segfault,程序可能会中止,或者有时它可以正常运行。或者,随便什么。没有办法保证结果

这些字符串在内存中的什么位置(堆栈或堆)


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

编辑1:

我将试着解释一下,你可以如何为自己找到答案。我不确定实际发生了什么,因为这是未定义的行为(如其他人所述),但您可以进行一些简单的调试,以了解编译器实际在做什么

原始答案

我的猜测是,它们都在堆栈上。您可以通过以下方式修改代码来检查这一点:

int main() {
  char c1 = 'X';
  char s1[] = "hello";
  char s2[] = "eheheheheheh"; 
  char c2 = '3';

  printf("%s\n", strcat(s1, s2));
}
c1
c2
将在堆栈上。知道您可以检查
s1
s2
是否也相同

如果
c1
的地址小于
s1
,并且
s1
的地址小于
c2
,则它位于堆栈上。否则,它可能位于
.bss
部分(这将是明智之举,但会破坏递归)

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

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

int main() {
  char s1[] = "hello";
  char s2[] = "eheheheheheh"; 
  printf("%s\n", strcat(s1, s2));
}
“hello”
(使用空终止符)被推到堆栈上,然后是
“eheheheh”
(使用空终止符)

它们都位于一个接一个的位置(多亏了您编写它们的顺序),形成了一个可以写入(但不应该写入!)的内存块。。。这就是没有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
命令从程序中转储所有符号。这些命令还应提供每个符号所在的部分

在文件中搜索
s1
s2
符号,如果找不到它们,则表示它们在堆栈上,但我们将在下一步中进行检查

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

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


如果发现它们的定义后跟
push
sub%esp
命令,则它们位于堆栈上。如果您不确定它们的定义是什么意思,请将其发回此处,让我们看看。

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

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