我有一些代码给你,希望有人能告诉我,我的错误。目前我正在将我的编程难题移植到其他编程语言中,以便获得一些动手。
C中的代码抽象(更新):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char *dummy = "1234567890";
const char* inlet = "_";
void dosomething(int c, char* s){
printf("%s\n", s);
if (c < 10) {
char *ns = malloc(sizeof(char)*11);
strncpy(ns, s, c-1);
strncat(ns, inlet, 1);
strcat(ns, &s[c]);
dosomething(c+1, ns);
//free(ns);
}
}
void main() {
for(int i = 0; i < 100; i++) {
char *s = malloc(sizeof(char)*11);
strcpy(s, dummy);
dosomething(1, s);
free(s);
}
}
代码运行得很好,直到我取消注释dosomething()方法中的free()调用。这就是我不理解的。正如我所看到的,释放内存绝对没有问题,因为在从递归调用返回后它不再被使用,但是programm输出告诉了不同的东西。
没有免费的输出符合预期:
...
1_34567890
1_34567890
...
第二个空闲时,只有一个结果,而程序停止时为:
*** Error in `./a.out': malloc(): memory corruption (fast): 0x000000000164e0d0 ***
Abgebrochen (Speicherabzug geschrieben)
更新: 我根据评论和答案更改了代码,但问题仍然存在。如果dosomething()方法中的free()调用是错误的,则使用malloc分配更多内存不会阻止内存错误。在递归的第一次迭代中正确生成输出,第二次显示不同的结果,第三次也是如此,然后程序失败(请参阅函数顶部的新printf以获得新结果:
输出:
1234567890
_234567890
__34567890
___4567890
____567890
_____67890
______7890
_______890
________90
_________0
1234567890
@@J_234567890
@@J_J_234567890
@@J__J_234567890
@@J___J_234567890
@@J___J_234567890
@@J___J_234567890
@@J____J_234567890
@@J____J_234567890
@@J_____0__234567890
1234567890
@@J_234567890
@@J_J_234567890
@@J__J_234567890
@@J___J_234567890
@@J___J_234567890
@@J___J_234567890
@@J____J_234567890
@@J____J_234567890
@@J_____0__234567890__234567890
*** Error in `./a.out': free(): invalid next size (fast): 0x00000000014a4130 ***
Abgebrochen (Speicherabzug geschrieben)
有人可以向我解释一下,我在眨眼吗?
UPDATE2: @Michi和@MichaelWalz已经解决了这个问题。它是使用malloc之间的组合 - 因此在第一次迭代后处理内存中的垃圾(打印内存地址以及字符串显示非常整洁),并使用strcat。
在未初始化的内存上使用strcat会将内存中的字符串追加到内存中指针后面的下一个“\ 0”字符。如果内存未初始化,则可能远远超出该字符串的范围。
谢谢你们!
工作代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char *dummy = "1234567890";
const char* inlet = "_";
void dosomething(int c, char* s){
printf("%p %s\n", s, s);
if (c < 10) {
//char *ns = malloc(sizeof(char)*11);
char *ns = calloc(11, sizeof(char));
strncpy(ns, s, c);
strncat(ns, inlet, 1);
strncat(ns, &s[c+1],10-c);
dosomething(c+1, ns);
free(ns);
}
}
void main() {
for(int i = 0; i < 100; i++) {
//char *s = malloc(sizeof(char)*11);
char *s = calloc(11, sizeof(char));
strcpy(s, dummy);
dosomething(0, s);
free(s);
}
}
答案 0 :(得分:9)
原因是因为 malloc 函数分配10个字符而需要11个字符(结束\0
)。
虽然这是依赖于实现的,但 malloc 函数可能是有效的,它使用分配区域内外的一些字节来设置一些内部信息。在此内部区域被更改(一个char太多)之后, free 可能会使用这些字节,最终结果是未定义的行为。
无论如何,改变甚至读取数据超出界限是 UB。
更好地使用
char *s = malloc(strlen(dummy) + 1);
并且不要强制生成malloc的结果指针。
答案 1 :(得分:4)
malloc()
通常不会初始化已分配的内存。
您应该使用memset()
来初始化已分配的内存或
使用calloc()
,用零初始化分配的内存。
你应该为11个字符分配内存(10 + 1用于&#39; \ 0&#39;)并且不要
转换malloc()
返回的指针。
在某些系统上,您可以配置malloc来初始化内存, 但这是一个最好的调试辅助工具,你永远不应该依赖它。
答案 2 :(得分:4)
strncpy(ns, s, c-1);
strncat(ns, inlet, 1);
问题出在这里。
每当您将c-1
字节从s
复制到ns
时,您永远不会复制终止\0
,因此在随后的strncat
调用中,{{1}的结尾无法识别(由于缺少ns
),因此来自入口的\0
可能会被复制,远远超出分配的_
字节11
覆盖记忆。
来自ns
警告:如果src的前n个字节中没有空字节,则放在dest中的字符串将不会以空值终止。
这正是每个man strncpy
调用中发生的事情导致strncpy(ns, s, c-1);
strncat
进一步导致内存损坏,strncat
可能会发现垃圾\0
字节远远超出11
个字节ns
。
正如使用calloc
而不是malloc
的另一个答案所指出的那样,将填充ns
指向的缓冲区\0
字节,因此strncpy(ns, s, c-1);
之后{ {1}} strncat
字符\0
字符c-1
字符ns
字符11
字符ns
\0
之后c-1
ns
strncpy(ns, s, c-1);
之前strncat(ns, inlet, 1);
明确地存储malloc
字符。char *ns = malloc(sizeof(char)*11);
strncpy(ns, s, c-1);
ns[c-1] = '\0';
strncat(ns, inlet, 1);
...
坚持<base>
。
因此,工作代码片段将是:
<head>
<base href="http://www.w3schools.com/images/" target="_blank">
</head>
答案 3 :(得分:3)
标准代码气味,任何答案都没有解决:
strncpy( char *dest, const char *src, size_t count )
这个函数有两个相当意想不到的功能,很多(大多数?)程序员都不知道:
count
,则dest
将填充零字节。 (这通常不是问题。)可是:
count
, dest
将不会以零终止。在查看您的程序流程后,我看到我的代码气味已确认:
第一次调用dosomething()
时,c
为1,ns
(指向新malloc()
内存)的内容不确定:
strncpy(ns, s, c-1);
复制零字节,包括否终止零字节。 ns
仍然指向完全不确定的内容。最重要的是,不能保证将分配给内存中的零字节。
出于这个原因,
strncat(ns, inlet, 1);
这是未定义的行为。
由于上述对溢出的影响,在继续之前始终断言dest[count - 1] == '\0'
。
答案 4 :(得分:1)
我对您的代码做了一些小改动:
将void main(){}
更改为int main(void){}
。
将int c
参数更改为size_t c
,因为strncpy需要
size_t
将malloc(sizeof(char)*11);
更改为calloc(11,1);
注释掉//free(ns);
。
我得到了:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char *dummy = "1234567890";
const char* inlet = "_";
void dosomething(size_t c, char* s);
int main(void) {
for(int i = 0; i < 100; i++) {
char *s = calloc(11,1);
strcpy(s, dummy);
dosomething(1, s);
free(s);
}
}
void dosomething(size_t c, char* s){
printf("%s\n", s);
if (c < 10) {
char *ns = calloc(11,1);
strncpy(ns, s, c-1);
strncat(ns, inlet, 1);
strcat(ns, &s[c]);
dosomething(c+1, ns);
free(ns);
}
}
答案 5 :(得分:1)
用strcat / strncat解决问题
首先,对免费的调用与代码中的问题无关。
我使用 Valgrind 来了解发生了什么,并且输出显示有条件跳转取决于未初始化的值:
== 4722 ==条件跳转或移动取决于未初始化的值
(在strncat的行上)
我做了一些研究,发现 strcat 和 strncat 需要 nul 终止字符才能正常工作(请参阅此post , 例如)。在调用 malloc 后,内存未初始化,
此外,对 strncpy 的调用不会添加终止字符,因为您始终复制(c-1)个字符,因此不包括空字节(请参阅strncpy的man页面,尤其是示例在注释部分)。因此,对 strncat 的调用可能会导致未定义的行为。
要解决此问题,在调用 strncat 函数之前,我们必须正确设置终止字符,如下面的代码片段所示:
void dosomething(int c, char* s){
printf("%d %s\n", c, s);
if (c < 10) {
char *ns = malloc(sizeof(char)*11);
if(c-1) strncpy(ns, s, c-1);
// ---- Set the nul character --- //
ns[c-1]='\0';
// ---- ---- ---- ---- ---- ---- //
strncat(ns, inlet, 2);
strcat(ns, &s[c]);
dosomething(c+1, ns);
free(ns);
}
}
我还添加了一个检查来执行第一个 strncat 只有当你真的需要复制一些东西时。更正后续调用 strcat 也更安全(另请参阅@rootkea的答案),因为您在 ns 字符串中添加了太多字符,边界(valgrind不报告这个)。
strcat(ns, &s[c]); ---> strncat(ns, &s[c], 10-c);