malloc和递归函数中的自由

时间:2016-01-08 08:23:35

标签: c pointers recursion

我有一些代码给你,希望有人能告诉我,我的错误。目前我正在将我的编程难题移植到其他编程语言中,以便获得一些动手。

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

6 个答案:

答案 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)

我对您的代码做了一些小改动:

  1. void main(){}更改为int main(void){}

  2. int c参数更改为size_t c,因为strncpy需要 size_t

  3. malloc(sizeof(char)*11);更改为calloc(11,1);

  4. 注释掉//free(ns);

  5. 我得到了:

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