错误在编写自己的realloc()实现时发生。

huangapple go评论62阅读模式
英文:

Error when coding my own implementation of realloc()

问题

以下是您要翻译的内容:

"I am contacting you because I need to code the functions realloc / strlen (signed and unsigned) / memcpy.
I have recoded these functions but it's doesn't working and now I have valgrind errors like Conditional jump or move depends on uninitialized value(s).
Can you help me to fix the problem?"

以下是您的函数:

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] = '
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);
}
'
;
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] = '
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);
}
'
;
char *clean = NULL; clean = my_str_clean(test); printf("%s\n", clean); free(clean); }

以下是Valgrind报告:

==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 uninitialized 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==  Uninitialized 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 uninitialized 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==  Uninitialized 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 uninitialized 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==  Uninitialized 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

<details>
<summary>英文:</summary>

I am contacting you because I need to code the functions `realloc` / `strlen` (signed and unsigned) / `memcpy`.
I have recoded these functions but it&#39;s doens&#39;t working and now I have valgrind errors like `Conditional jump or move depends on uninitialised value(s)`.
Can you help me to fix the problem?

Here are the functions:

```c
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 &lt; 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 &lt; n &amp;&amp; i &lt; 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;
}

Currently I test these functions via the following functions:

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 != &#39; &#39; &amp;&amp; *ptr != &#39;\t&#39; &amp;&amp; *ptr != &#39;\n&#39;) {
            new_str = my_realloc(new_str, (sizeof(char) * (i + 2)));
            new_str[i] = *ptr;
            i++;
        }
        ptr++;
    }
    new_str[i] = &#39;\0&#39;;
    free(str);
    return (new_str);
}

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

Here is the valgrid report :

==28637== Memcheck, a memory error detector
==28637== Copyright (C) 2002-2022, and GNU GPL&#39;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)

答案1

得分: 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;

标准或你的my_strlen_unsigned版本工作在字符串上,即假定指针指向一个以空字节结尾的char数组。一个int数组不会以空字节结尾。你的my_realloc()因此会引发未定义行为。


可能的修复:

你不能在至少重新实现自己的malloc()free()的情况下确定ptr指向的块的旧大小并重新实现realloc()。但你可以将旧大小作为第三个参数。 (但标准的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 its 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()之下,引发了未定义的行为。因此出现了警告。


英文:

Invalid assumption:

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

You're breaking the realloc() specification here. realloc() doesn't assume that the ptr would always be pointing to a string. It is type-agnostic.

And the cast is redundant. There's an implicit conversion from void * to any other pointer type.


Undefined behaviour:

old_size = my_strlen_unsigned(old_ptr) + 1;

The standard, or your my_strlen_unsigned version of it, works with strings, i.e. the pointer is assumed to be pointing to an array of char terminated by a null-byte. An array of ints will not be terminated with a null-byte. Your my_realloc() thus invokes undefined behaviour.


Possible fix:

You can't determine the old size of the block pointed to by ptr and reimplement realloc(), without at least reimplementing your own malloc() and free(), portably. But you can take the old size as the third argument. (But the standard realloc() only takes two. So would this be a conforming implementation?)

Here's glibc's implementation of it: malloc().c

Here's musl C implementation of it: malloc().c

And here's my toy example that attempts to emulate the standard realloc() to some extent:

/** 
*    @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&#39;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 &lt;= old_size) {
return ptr;
}
/* As a last resort, allocate a new chunk and copy to it. 
*/
void *new = 0;
if (new_size &gt; old_size) {
new = malloc (new_size);
if (!new) {
return 0;
}
memcpy (new, ptr, old_size);
free (ptr);
}
return new;
}

You can also find a sample implementation in chapter 8 of K&R.


Side-notes:

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

You risk losing access to the original memory allocated through malloc() here. If my_realloc() returned NULL, new_str would be assigned it's result and you would cause a memory leak in your program.

Furthermore, the memory returned by malloc() is uninitialized. And your code invokes undefined behaviour by calling my_strlen() underneath my_realloc() on a pointer that is uninitialized. Hence the warnings.


答案2

得分: 1

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

如果你无法这样做,可能有一些其他方法来解决这个问题。例如,malloc_usable_size()函数是GNU扩展,可用于查询现有数据块的大小,因此你可以使用它代替你的my_strlen_unsigned()。但请注意,如果你使用动态链接(编译时的默认选项),这将使你的程序不具备可移植性,并且只能在使用glibc的系统上工作。在这种情况下,你可能希望以静态方式链接你的程序。

假设你的代码中的其他函数(如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;
}
英文:

Your are assuming that data in the chunk is a correctly terminated C string, and using my_strlen_unsigned() to calculate the old size. You cannot do this since you cannot know what kind of data is stored in a chunk. The only real solution is to also remember the chunk size somehow, and pass it as parameter to your function.

If you cannot do this, there might be some other ways to get around the problem. For example, the malloc_usable_size() function is a GNU extension available in glibc (GNU libc) that can be used to query the size of an existing chunk, so you can use that instead of your my_strlen_unsigned(). Note however that if you use dynamic linking (the default when compiling) this will make your program non-portable and will only work on systems using glibc. You may wish to link your program statically in this case.

Assuming that the other functions in your code (such as my_memcpy()) are correctly implemented, a correct implementation of my_realloc() would be as follows:

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 &lt; size ? old_size : size);
        free(ptr);
    }

    return new_ptr;
}

huangapple
  • 本文由 发表于 2023年2月16日 18:54:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75471233.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定