(void *) 的 realloc 中的内存泄漏以连接字符串

Posted

技术标签:

【中文标题】(void *) 的 realloc 中的内存泄漏以连接字符串【英文标题】:Memory leak in realloc of (void *) to concatenate string 【发布时间】:2020-11-09 03:08:45 【问题描述】:

给定一个带有 void 指针 (void *) value 的结构对象,该指针使用 malloc 初始化以保存字符串 "chapt"。 之后,使用realloc 腾出足够的内存来连接另一个字符串。

/* Standard Imports */
#include <stdio.h>      
#include <stdlib.h>     
#include <string.h> 
#include <assert.h>

struct generic_type
    
        void *value;                            
        void (*add)(struct generic_type, int);  
    ;

/* Function Declarations */
static void TestRun();
static void AddNumToString(struct generic_type element, int num);

#define TEST_ARRAY_SIZE 1

int main(int argc, char *argv[])

    TestRun();
    
    (void) argc;
    (void) *argv;

    return 0;


static void TestRun()

    struct generic_type element;

    element.value = malloc(sizeof(char) * 6);
    assert (NULL != element.value);
    element.value = strcpy(element.value, "chapt");
    element.add = AddNumToString;

    element.add(element, 10);
    free(element.value);


static void AddNumToString(struct generic_type element, int num)

    size_t num_length = snprintf(NULL, 0, "%d", num);
    size_t str_length = strlen((char *)(element.value));
    size_t new_length = str_length + num_length + 1;
    char *num_string = (char *)malloc(sizeof(char) * (num_length + 1));
    
    sprintf(num_string, "%d", num);
    
    element.value = realloc(element.value, sizeof(char) * new_length);
    assert (NULL != element.value);
    
    element.value = strcat(((char *)(element.value)), num_string);
    
    free(num_string);

此实现会产生正确的输出,但存在内存泄漏:

==29031== Memcheck, a memory error detector
==29031== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==29031== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==29031== Command: ./a.out
==29031== 
==29031== Invalid free() / delete / delete[] / realloc()
==29031==    at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29031==    by 0x1088EB: TestRun (teststructs.c:40)
==29031==    by 0x108862: main (teststructs.c:22)
==29031==  Address 0x522d040 is 0 bytes inside a block of size 6 free'd
==29031==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29031==    by 0x108999: AddNumToString (teststructs.c:52)
==29031==    by 0x1088DF: TestRun (teststructs.c:39)
==29031==    by 0x108862: main (teststructs.c:22)
==29031==  Block was alloc'd at
==29031==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29031==    by 0x10887B: TestRun (teststructs.c:34)
==29031==    by 0x108862: main (teststructs.c:22)
==29031== 
==29031== 
==29031== HEAP SUMMARY:
==29031==     in use at exit: 8 bytes in 1 blocks
==29031==   total heap usage: 3 allocs, 3 frees, 17 bytes allocated
==29031== 
==29031== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1
==29031==    at 0x4C31D2F: realloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29031==    by 0x108999: AddNumToString (teststructs.c:52)
==29031==    by 0x1088DF: TestRun (teststructs.c:39)
==29031==    by 0x108862: main (teststructs.c:22)
==29031== 
==29031== LEAK SUMMARY:
==29031==    definitely lost: 8 bytes in 1 blocks
==29031==    indirectly lost: 0 bytes in 0 blocks
==29031==      possibly lost: 0 bytes in 0 blocks
==29031==    still reachable: 0 bytes in 0 blocks
==29031==         suppressed: 0 bytes in 0 blocks
==29031== 
==29031== For counts of detected and suppressed errors, rerun with: -v
==29031== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

似乎问题出在realloc 行,但我似乎看不出它的问题。

在初始化期间分配足够的内存并避免realloc 可以解决问题,但我很清楚为什么此时这不起作用。

【问题讨论】:

请创建一个minimal reproducible example。 struct sgeneric 是什么?函数是怎么调用的?你如何初始化所有相关的指针?无效访问发生在哪里?是读还是写?为什么你到处都放(char *)has a memory leak: 请展示 valgrind 向您展示的内容。应该有整个堆栈,包括附加信息。您刚刚发布了有关一些地址的信息。作为旁注,只需将snprintf 转换为element.value,不需要num_string 您显示的代码对我来说看起来不错,因此错误可能在其他地方。我同意你应该创建一个最小的例子。 您的问题几乎可以肯定是 FreeStructWithString 中的代码使用 realloc 调用之前的指针值。 这仍然不是一个最小的可重现示例。我们正在寻找一个可以编译和运行的完整程序。 \0" - \0 之一被忽略,只需删除 \0ws8_main.c:100 看起来像一个相当短的文件 - 您可以考虑将其全部发布。 (我看到您的函数指针按值获取结构。您不会偶然调用 realloc 并修改 void (*add)(struct sgeneric,..) 中的 sgeneric::value 吗?) 【参考方案1】:

AddNumToString 按值传递其element 参数,以便它获得传递给它的对象的副本。这意味着当你这样做时

element.value = realloc(element.value, sizeof(char) * new_length);

element 中包含的原始指针被释放,但新指针存储在副本中。当AddNumToString 返回时副本丢失,因此新分配的空间泄漏。更糟糕的是,调用者中的对象保持不变;特别是,它仍然包含现在已被释放的原始指针。因此,当它最终被释放时(未在您当前的代码中显示),这是双重释放,这很糟糕。

您可能希望AddNumToString 改为使用指向struct generic_type 的指针,以便它可以实际修改对象。

【讨论】:

我尝试在函数调用前后检查element.value的地址和长度,它返回相同的地址并且长度增加了正确的长度。这真的让我很反感。感谢您的详细回答。

以上是关于(void *) 的 realloc 中的内存泄漏以连接字符串的主要内容,如果未能解决你的问题,请参考以下文章

动态分配内存-realloc

堆的分配和释放(malloc,free,calloc,realloc)

C内存分配

realloc函数详解

如何在linux下检测内存泄漏

动态内存函数+经典笔试题@动态内存管理---malloc +free + calloc + realloc