Ошибка при кодировании моей собственной реализации realloc()

Я связываюсь с вами, потому что мне нужно закодировать функции realloc/strlen (со знаком и без знака)/memcpy. Я перекодировал эти функции, но они не работают, и теперь у меня есть ошибки valgrind, такие как Conditional 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)

Что такое my_strlen_unsigned? Гарантируется ли, что данные будут «строкой», которая может быть надежно подсчитана этой функцией?

Some programmer dude 16.02.2023 11:57

«функция realloc должна иметь тот же прототип, что и официальная функция, и работать для любого типа указателя» — тогда вы определенно не можете предположить, что указатель является unsigned char *, указывающим на действительную завершенную строку C, не так ли? Другими словами, my_strlen_unsigned использовать нельзя.

Marco Bonelli 16.02.2023 11:59

@ВирджилГ. вы не можете. Вам также придется передать функции старый размер. Способ malloc заключается в том, что метаданные чанка (размер и другие вещи) хранятся прямо перед чанком, поэтому внутренне он знает размер, даже не передавая его явно. Однако у вас нет доступа к этой информации. Если вы хотите написать свой собственный распределитель, вам, вероятно, следует также перереализовать malloc таким образом, чтобы вы могли хранить размер где-то в чанке.

Marco Bonelli 16.02.2023 12:02

См. этот мой ответ для получения дополнительной информации. Вы должны иметь возможность запросить размер с помощью malloc_usable_size(), но это расширение GNU, поэтому, если вы его используете, код не будет переносимым и будет работать только в системе, использующей glibc (GNU libc).

Marco Bonelli 16.02.2023 12:03

Хорошо, большое спасибо, но не могли бы вы написать мне правильную функцию realloc/memcpy, которая не использует unsigned char * и использует malloc_usable_size()?

Virgil G. 16.02.2023 12:06

Я бы порекомендовал вам взглянуть на реализацию musl C. Он довольно маленький и аккуратный. glibc сложен из-за необходимости поддерживать множество ОС и платформ.

Paul Floyd 16.02.2023 15:31
Руководство для начинающих по веб-разработке на React.js
Руководство для начинающих по веб-разработке на React.js
Веб-разработка - это захватывающая и постоянно меняющаяся область, которая постоянно развивается благодаря новым технологиям и тенденциям. Одним из...
Разница между Angular и React
Разница между Angular и React
React и AngularJS - это два самых популярных фреймворка для веб-разработки. Оба фреймворка имеют свои уникальные особенности и преимущества, которые...
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц....
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
1
6
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы предполагаете, что данные в чанке представляют собой правильно завершенную строку C, и используете my_strlen_unsigned() для вычисления старого размера. Вы не можете этого сделать, так как не можете знать, какие данные хранятся в чанке. Единственное реальное решение - также каким-то образом запомнить размер фрагмента и передать его в качестве параметра вашей функции.

Если вы не можете этого сделать, могут быть другие способы обойти проблему. Например, функция malloc_usable_size() — это расширение GNU, доступное в glibc (GNU libc), которое можно использовать для запроса размера существующего фрагмента, поэтому вы можете использовать его вместо своего 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;
}
Ответ принят как подходящий

Неверное предположение:

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, оканчивающийся нулевым байтом. Массив ints не будет заканчиваться нулевым байтом. Таким образом, ваш my_realloc() вызывает неопределенное поведение.


Возможное исправление:

Вы не можете определить старый размер блока, на который указывает ptr, и повторно реализовать realloc(), по крайней мере, не переопределив свои собственные malloc() и free(), переносимым образом. Но в качестве третьего аргумента можно взять старый размер. (Но стандарт realloc() принимает только два. Будет ли это соответствующей реализацией?)

Вот реализация glibc: malloc().c

Вот его реализация на 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;
}


Дополнительные примечания:

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

Вы рискуете потерять доступ к исходной памяти, выделенной через malloc() здесь. Если my_realloc() вернет NULL, new_str будет присвоен его результат, и вы вызовете утечку памяти в вашей программе.

Кроме того, память, возвращаемая malloc(), не инициализирована. И ваш код вызывает неопределенное поведение, вызывая my_strlen() под my_realloc() для неинициализированного указателя. Отсюда и предупреждения.


Другие вопросы по теме