C 中止,而不是清除内存冲突的segfault
我在处理C字符串时遇到了这种奇怪的行为。这是K&R书中的一个练习,我应该在其中编写一个函数,将一个字符串附加到另一个字符串的末尾。这显然需要为目标字符串分配足够的内存,以便源字符串适合。代码如下: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
/* 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[]中的每个字符向前移动一个字节。堆栈作为堆栈是可读写的。所以没有限制什么