Я связываюсь с вами, потому что мне нужно закодировать функции 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)
«функция realloc должна иметь тот же прототип, что и официальная функция, и работать для любого типа указателя» — тогда вы определенно не можете предположить, что указатель является unsigned char *, указывающим на действительную завершенную строку C, не так ли? Другими словами, my_strlen_unsigned использовать нельзя.
@ВирджилГ. вы не можете. Вам также придется передать функции старый размер. Способ malloc заключается в том, что метаданные чанка (размер и другие вещи) хранятся прямо перед чанком, поэтому внутренне он знает размер, даже не передавая его явно. Однако у вас нет доступа к этой информации. Если вы хотите написать свой собственный распределитель, вам, вероятно, следует также перереализовать malloc таким образом, чтобы вы могли хранить размер где-то в чанке.
См. этот мой ответ для получения дополнительной информации. Вы должны иметь возможность запросить размер с помощью malloc_usable_size(), но это расширение GNU, поэтому, если вы его используете, код не будет переносимым и будет работать только в системе, использующей glibc (GNU libc).
Хорошо, большое спасибо, но не могли бы вы написать мне правильную функцию realloc/memcpy, которая не использует unsigned char * и использует malloc_usable_size()?
Я бы порекомендовал вам взглянуть на реализацию musl C. Он довольно маленький и аккуратный. glibc сложен из-за необходимости поддерживать множество ОС и платформ.
Вы предполагаете, что данные в чанке представляют собой правильно завершенную строку 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() для неинициализированного указателя. Отсюда и предупреждения.
Что такое my_strlen_unsigned? Гарантируется ли, что данные будут «строкой», которая может быть надежно подсчитана этой функцией?