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 int
s 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'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;
}
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.
solved Error when coding my own implementation of realloc()