动态内存分配中的C编程问题

问题描述

问题应该很简单,但我花了几个小时来解决这个问题,但看不出我的逻辑有什么问题。输出正常工作,但 Valgrind 打印应该修复的内存问题。我已将 origdest = (char*)realloc(origdest,strlen(origdest) + i * sizeof(char)); 代码添加到 while 循环中,我的问题是为什么不动态调整内存? Valgrind 给出的准确错误

==9== Invalid write of size 1
==9==    at 0x1087E2: mystrcat (mystrcat.c:18)
==9==    by 0x10883C: main (mystrcat.c:34)
==9==  Address 0x522d046 is 6 bytes inside a block of size 7 free'd
==9==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9==    by 0x1087C2: mystrcat (mystrcat.c:17)
==9==    by 0x10883C: main (mystrcat.c:34)
==9==  Block was alloc'd at
==9==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9==    by 0x108811: main (mystrcat.c:31)
char *mystrcat(char *dest,const char *src)
{
    char *origdest = dest;
    
    while(*dest) {
        dest++;
    }
    
    int i = 1;

    while (*src) {
        origdest = (char*)realloc(origdest,strlen(origdest) + i * sizeof(char));
        *dest++ = *src++;  // copies character and increases/moves pointer
        i++;
    }
    
    *dest = 0;

    return origdest;
}

int main(void)
{
    char *str = malloc(7);
    strcpy(str,"Mydogs");

    str = mystrcat(str,"arecool");
    printf("%s\n",str);
    free(str);
}

解决方法

本声明:

Address 0x522d046 is 6 bytes inside a block of size 7 free'd 表示在这些语句之后调用的 realloc() 导致旧指针指向已释放的内存。

在此段之后:

char *origdest = dest;
    
while(*dest) {
        dest++;
}

编辑以解决评论“代码有什么特别的问题以及可以更改哪些内容以使其正常工作?”

我在上面的第一个观察结果的解释是,一旦指向已分配内存的指针移动,就像您所做的那样,内存分配表不再具有该内存的准确位置,从而使该内存不可释放。

您在此处声明的目标是创建 strcat() 的版本,因此使用 realloc() 是一种合理的方法,但要安全地使用它,请先将新内存分配到临时缓冲区中,然后如果分配失败,原来的内存位置还存在,可以释放。
另一个产生重大影响的小变化是 i 的初始化方式。如果使用 1,它会将第二个字符串的开头在内存中多放一个位置,在第一个字符串之后留下一个 \0 字符,从而有效地使其成为结果字符串的结尾。即您永远不会看到字符串的附加部分: 在内存中它看起来像这样:

|M|y|d|o|g|s|\0|a|r|e|c|o|o|l|

然后在尝试在串联缓冲区的 和 处放置另一个 NULL 终止符时溢出缓冲区,从而导致未定义的行为。

您的代码的以下改编说明了这些以及其他一些简化:

char *mystrcat(char *dest,const char *src)
{
    char *temp = NULL;
    
    int i = 0;//changed from i = 1 as first location to 
              //copy to is len1,not len1 + 1
              //Note,starting at len1 + 1 would leave a NULL character
              //after "Mydogs",effectively ending the string
    //the following are simplifications for use in realloc()
    int len1 = strlen(dest);
    int len2 = strlen(src);

    //call once rather than in a loop.  It is more efficient.
    temp = realloc(dest,len1+len2+1);//do not cast return of realloc
    if(!temp)   
    {
        //handle error 
        return NULL;
    }
    dest = temp;
    while(*src)
    {
        dest[len1 + i] = *src;
        i++;
        src++;
    }
    dest[len1 + i] = 0;//add null terminator

    return dest;     
}

    int main(void)
    {
        char *temp = NULL;          
        char *str = malloc(7);
        if(str)//always a good idea to test pointer before using
        {
            strcpy(str,"Mydogs");
            temp = mystrcat(str,"arecool");
            if(!temp)
            {
                free(str);
                printf("memory allocation error,leaving early");
                return 0;
            }
            str = temp;
            printf("%s\n",str);
            free(str);
        }
        return 0;
    }

为什么是 not correct to cast the return of c-m-realloc() in C

,

这里移动到原始字符串的末尾:

    while(*dest)
        dest++;

这里分配了一些新的内存,但 dest 仍然指向原始字符串的末尾。所以你在原始字符串结束后覆盖内存。由于您正在重新分配,原始字符串甚至可能不再存在于您之前写入的位置,因为 realloc 可以将数据移动到一个全新的位置。

    while (*src)
    {
        origdest = (char*)realloc(origdest,strlen(origdest) + i * sizeof(char));
        *dest++ = *src++;  // Copies character and increases/moves pointer
        i++;
    }