编写我自己的 realloc() 实现时出错

Error when coding my own implementation of realloc()

提问人:Virgil G. 提问时间:2/16/2023 最后编辑:Virgil G. 更新时间:2/17/2023 访问量:194

问:

我正在联系您,因为我需要编写函数 /(有符号和无符号)/ 。 我已经重新编码了这些函数,但它不起作用,现在我有 valgrind 错误,例如 . 你能帮我解决这个问题吗?reallocstrlenmemcpyConditional jump or move depends on uninitialised value(s)

以下是功能:

void *my_realloc(void *ptr, size_t size)
{
    unsigned char *old_ptr = (unsigned char *)ptr;
    void *new_ptr = NULL;
    size_t old_size = 0;
    if (!ptr) {
        return malloc(size);
    }
    if (size == 0) {
        free(ptr);
        return NULL;
    }
    old_size = my_strlen_unsigned(old_ptr) + 1;
    new_ptr = malloc(size);
    if (!new_ptr) {
        return NULL;
    }
    my_memcpy(new_ptr, ptr, old_size < size ? old_size : size);
    free(ptr);
    return new_ptr;
}
void *my_memcpy(void *restrict dest, const void *restrict src, size_t n)
{
    if (!dest || !src) return NULL;
    unsigned char *d = dest;
    const unsigned char *s = src;
    size_t i = 0;
    while (i < n && i < my_strlen_unsigned(s)) {
        *d = *s;
        d++;
        s++;
        i++;
    }
    return dest;
}
size_t my_strlen_unsigned(const unsigned char *s)
{
    size_t count = 0;
    if (s != NULL) {
        while (*s != 0) {
            count++;
            s++;
        }
    }
    return count;
}
size_t my_strlen(const char *s)
{
    size_t count = 0;
    if (s != NULL) {
        while (*s != 0) {
            count++;
            s++;
        }
    }
    return count;
}

目前,我通过以下函数测试这些函数:

char *my_str_clean(char *str)
{
    char *ptr = str;
    char *new_str = malloc(1);
    size_t i = 0;
    if (!new_str)
        return (NULL);
    while (*ptr) {
        if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
            new_str = my_realloc(new_str, (sizeof(char) * (i + 2)));
            new_str[i] = *ptr;
            i++;
        }
        ptr++;
    }
    new_str[i] = '\0';
    free(str);
    return (new_str);
}

int main(int argc, char **argv, char **env)
{
    char *test = malloc(15);
    test[0] = 'l';
    test[1] = 's';
    test[2] = ' ';
    test[3] = ' ';
    test[4] = ' ';
    test[5] = ' ';
    test[6] = ' ';
    test[7] = ' ';
    test[8] = ' ';
    test[9] = '-';
    test[10] = 'l';
    test[11] = ' ';
    test[12] = '-';
    test[13] = 'a';
    test[14] = '\0';
    char *clean = NULL;
    clean = my_str_clean(test);
    printf("%s\n", clean);
    free(clean);
}

这是valgrid报告:

==28637== Memcheck, a memory error detector
==28637== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==28637== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==28637== Command: ./mysh
==28637== 
==28637== Conditional jump or move depends on uninitialised value(s)
==28637==    at 0x109BB2: my_strlen_unsigned (my_strlen_unsigned.c:14)
==28637==    by 0x109B2B: my_realloc (my_realloc.c:35)
==28637==    by 0x1096B5: my_str_clean (my_str_clean.c:19)
==28637==    by 0x10954A: main (minishell.c:55)
==28637==  Uninitialised value was created by a heap allocation
==28637==    at 0x4841888: malloc (vg_replace_malloc.c:381)
==28637==    by 0x109660: my_str_clean (my_str_clean.c:13)
==28637==    by 0x10954A: main (minishell.c:55)
==28637== 
==28637== Conditional jump or move depends on uninitialised value(s)
==28637==    at 0x109BB2: my_strlen_unsigned (my_strlen_unsigned.c:14)
==28637==    by 0x109ABC: my_memcpy (my_memcpy.c:16)
==28637==    by 0x109B73: my_realloc (my_realloc.c:40)
==28637==    by 0x1096B5: my_str_clean (my_str_clean.c:19)
==28637==    by 0x10954A: main (minishell.c:55)
==28637==  Uninitialised value was created by a heap allocation
==28637==    at 0x4841888: malloc (vg_replace_malloc.c:381)
==28637==    by 0x109660: my_str_clean (my_str_clean.c:13)
==28637==    by 0x10954A: main (minishell.c:55)
==28637== 
==28637== Conditional jump or move depends on uninitialised value(s)
==28637==    at 0x1097A5: my_strlen (my_strlen.c:18)
==28637==    by 0x10962C: my_put_str (my_put_str.c:21)
==28637==    by 0x10955F: main (minishell.c:56)
==28637==  Uninitialised value was created by a heap allocation
==28637==    at 0x4841888: malloc (vg_replace_malloc.c:381)
==28637==    by 0x109B3F: my_realloc (my_realloc.c:36)
==28637==    by 0x1096B5: my_str_clean (my_str_clean.c:19)
==28637==    by 0x10954A: main (minishell.c:55)
==28637== 
l==28637== 
==28637== HEAP SUMMARY:
==28637==     in use at exit: 0 bytes in 0 blocks
==28637==   total heap usage: 8 allocs, 8 frees, 43 bytes allocated
==28637== 
==28637== All heap blocks were freed -- no leaks are possible
==28637== 
==28637== For lists of detected and suppressed errors, rerun with: -s
==28637== ERROR SUMMARY: 18 errors from 3 contexts (suppressed: 0 from 0)
c valgrind realloc libc

评论

0赞 Some programmer dude 2/16/2023
什么?数据是否保证是该函数可以可靠计数的“字符串”?my_strlen_unsigned
0赞 Marco Bonelli 2/16/2023
“realloc 函数必须与官方函数具有相同的 prorotype,并且适用于任何指针类型”——那么你绝对不能假设指针指向一个有效的终止 C 字符串,对吗?换句话说,不能使用。unsigned char *my_strlen_unsigned
1赞 Marco Bonelli 2/16/2023
@VirgilG。你不能。您还必须将旧大小传递给函数。这样做的方式是,块的元数据(大小和其他内容)存储在块之前,因此即使没有显式传递它,它内部也知道大小。但是,您无权访问此信息。如果你想编写自己的分配器,你可能也应该重新实现,这样你就可以将大小存储在块中的某个位置。mallocmalloc
0赞 Marco Bonelli 2/16/2023
有关更多信息,请参阅我的这个答案。您应该能够使用 查询大小,但它是一个 GNU 扩展,因此如果您使用它,代码将无法移植,并且只能在使用 glibc (GNU libc) 的系统上运行。malloc_usable_size()
0赞 فِرجِيل 2/16/2023
好的,非常感谢,但是你能给我写一个不使用无符号 char * 并使用 malloc_usable_size() 的正确 realloc / memcpy 函数吗?

答:

1赞 Marco Bonelli 2/16/2023 #1

你假设块中的数据是正确终止的 C 字符串,并用于计算旧大小。你不能这样做,因为你不知道块中存储了什么样的数据。唯一真正的解决方案是以某种方式记住块大小,并将其作为参数传递给您的函数。my_strlen_unsigned()

如果无法执行此操作,则可能还有其他方法可以解决此问题。例如,malloc_usable_size() 函数是 glibc (GNU libc) 中提供的 GNU 扩展,可用于查询现有块的大小,因此您可以使用它来代替 .但请注意,如果您使用动态链接(编译时的默认设置),这将使您的程序不可移植,并且只能在使用 glibc 的系统上运行。在这种情况下,您可能希望静态链接您的程序。my_strlen_unsigned()

假设代码中的其他函数(如 )已正确实现,则 的正确实现如下:my_memcpy()my_realloc()

void *my_realloc(void *ptr, size_t size)
{
    void *new_ptr;
    size_t old_size;

    if (!ptr)
        return malloc(size);

    if (size == 0) {
        free(ptr);
        return NULL;
    }

    new_ptr  = ptr;
    old_size = malloc_usable_size(ptr);
    
    if (size != old_size) {
        new_ptr = malloc(size);
        if (!new_ptr)
            return NULL;

        my_memcpy(new_ptr, ptr, old_size < size ? old_size : size);
        free(ptr);
    }

    return new_ptr;
}
2赞 Harith 2/16/2023 #2

无效的假设:

void *my_realloc(void *ptr, size_t size)
{
    unsigned char *old_ptr = (unsigned char *)ptr;

你在这里违反了规范。 不假定 将始终指向字符串。它与类型无关。realloc()realloc()ptr

而且演员阵容是多余的。存在从到任何其他指针类型的隐式转换。void *


未定义的行为:

old_size = my_strlen_unsigned(old_ptr) + 1;

标准或您的版本适用于字符串,即假定指针指向以 null 字节结尾的数组。s 数组不会以 null 字节结尾。因此,你的调用了未定义的行为。my_strlen_unsignedcharintmy_realloc()


可能的修复:

您无法确定 和 重新实现 的旧大小,而至少不能重新实现您自己的 和 。但是您可以将旧尺寸作为第三个参数。(但标准只需要两个。那么这会是一个符合要求的实现吗?ptrrealloc()malloc()free()realloc()

这是 glibc 对它的实现:malloc().c

这是它的 musl C 实现:malloc().c

这是我的玩具示例,它试图在某种程度上模仿该标准:realloc()

/** 
*    @brief The my_realloc() function shall deallocate the old object pointed to
*           by ptr and return a pointer to a new object that has the size specified by new_size.
*
*    @param ptr - A pointer to a block of memory to resize.
*    @param old_size - The size of the block pointed to by ptr.
*    @param new_size - The size to resize by.
*
*    If ptr is a null pointer, my_realloc() shall be equivalent to
*    malloc() for the specified new_size.
*
*    If ptr does not match a pointer returned earlier by calloc(),
*    malloc(), or realloc() or if the space has previously been
*    deallocated by a call to free() or realloc(), the behavior is undefined.
*    
*    @return Upon successful completion, my_realloc() shall return a pointer to the moved allocated space.  
*            If size and ptr both evaluate to 0, my_realloc() shall return a 
*            NULL pointer with errno set to [EINVAL].
*            If there is not enough available memory, my_realloc() shall return a
*            NULL pointer and set errno to [ENOMEM].
*            
*            If my_realloc() returns a NULL pointer, the memory referenced by ptr shall not be changed.
*
*    @warning my_realloc() may return NULL to indicate an error. For that reason, a different pointer variable 
*             must be used to hold it's return value. Otherwise, you risk overwriting the original ptr with NULL and 
*             losing your only reference to the original block of memory.
*/ 
              
void *my_realloc (void *ptr, size_t new_size, size_t old size) 
{
    if (!ptr) {
        return malloc (new_size);
    }
    
    if (!new_size) {
        errno = EINVAL;
        return 0;
    }
    
    if (new_size <= old_size) {
        return ptr;
    }

   /* As a last resort, allocate a new chunk and copy to it. 
    */
    void *new = 0;
    if (new_size > old_size) {
        new = malloc (new_size);
        if (!new) {
            return 0;
        }
        memcpy (new, ptr, old_size);
        free (ptr);
    }
    return new;
}

您还可以在 K&R 的第 8 章中找到示例实现。


旁注:

char *new_str = malloc(1);
new_str = my_realloc(..);

您可能会失去对通过此处分配的原始内存的访问。如果返回 ,将被分配其结果,并且会导致程序内存泄漏。malloc()my_realloc()NULLnew_str

此外,返回的内存是未初始化的。代码通过调用未初始化的指针来调用未定义的行为。因此发出警告。malloc()my_strlen()my_realloc()