0

I have a variable where the size is determined in run-time.

Is it generally better to realloc it every time a new element is added like:

array_type * someArray;
int counter = 0;

//before a new element is added:
counter ++;
someArray = realloc(someArray, counter * sizeof(array_type));

Or allocate more memory than probably needed using malloc once:

array_type * someArray = malloc(ENOUG_MEMORY * sizeof(array_type));

What is best in terms of efficiency(speed), readability and memory-management? And why?

Philip F.
  • 1,167
  • 1
  • 18
  • 33
  • 2
    I think this depends on the use case. – kiner_shah Dec 27 '21 at 10:12
  • Another option is to grow it by some chosen percentage or some chosen number of elements every time it gets "full" and we need to add to it. – David Schwartz Dec 27 '21 at 10:13
  • 1
    Generally memory (re)allocation is an heavy process (because can involve OS if local pool is low). The standard technique is to define a chunk of memory to allocate for each expansion, limiting in this way the access to system resource. Of course if the reallocation is supposed to happen on start of a process or to be limited in number, you can decide to proceed with single reallocations. As always it depends on the context. P.S. on some small, or embedded, systems memory allocation can influence the whole system performance or can lead to memory shortage. In those cases the strategy is forced – Frankie_C Dec 27 '21 at 10:36

4 Answers4

2

realloc could occasionally be useful in the past, with certain allocation patterns in single-threaded code. Most modern memory managers are optimized for multi-threaded programs and to minimize fragmentation, so, when growing an allocation, a realloc will almost certainly just allocate a new block, copy the existing data, and then free the old block. So there's no real advantage to trying to use realloc.

Increasing the size one element at a time can create an O(n^2) situation. In the worst case, the existing data has to be copied each time. It would be better to increase the size in chunks (which is still O(n^2) but with a smaller constant factor) or to grow the allocation geometrically (which gives an O(n) amortized cost).

Furthermore, it's difficult to use realloc correctly.

someArray = realloc(someArray, counter * sizeof(array_type));

If the realloc fails, someArray is set to NULL. If that was your only copy of the pointer to the previously allocated memory, you've just lost it. You won't be able to access the data you had already placed, and you can't free the original allocation, so you'll have a memory leak.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
2

What is best in terms of efficiency(speed), readability and memory-management? And why?

There is no general best. Specific best depends on your specific application and use case and environment. You can throw wars over perfect realloc ratio and decide if you need realloc at all.

Remember about rules of optimization. You do not optimize. Then, you do not optimize, without measuring first. The best in any terms can be measured for your specific setup, your specific environment, your specific application that uses specific operating system and *alloc implementation.

what is the best practice?

Allocating a constant amount of memory (if it's small enough) is static. It's just an array. Refactor you application to just:

array_type someArray[ENOUGH_MEMORY];

If you do not want to over allocate (or ENOUGH_MEMORY is big enough), then use realloc to add one element, as presented.

If you want, "optimize" by not calling realloc that often and over allocating - it seems that ratio 1.5 is the most preferred from the linked thread above. Still, it's highly application specific - I would over allocate on Linux, I would not when working on STM32 or other bare metal.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
1

I would use realloc with caution. Calling realloc in general leads to:

  1. allocating completely new block
  2. copying all data from old to new location
  3. releasing (freeing) the initial block.

All combined could be questionable from performance perspective, depending on the app, volume of data, response requirements.

In addition, in case of realloc failure, the return value is NULL which means that allocation to new block is not straightforward (indirection is required). E.g.

    int *p = malloc(100 * sizeof *p);
    if (NULL == p) 
    {
        perror("malloc() failed");
        return EXIT_FAILURE;
    }
 
    do_something_with_p(p);

    /* Reallocate array to a new size
     * Using temporary pointer in case realloc() fails. */
    {
        int *temp = realloc(p, 100000 * sizeof *temp);

        if (NULL == temp)
        {
            perror("realloc() failed");
            free(p);
            return EXIT_FAILURE;
        }      

        p = temp;
    }
0

malloc vs realloc - what is the best practice?

Helper functions

When writing robust code, I avoid using library *alloc() functions directly. Instead I form helper functions to handle various use cases and take care of edge cases, parameter validation, etc.

Within these helper functions, I use malloc(), realloc(), calloc() as building blocks, perhaps steered by implementation macros, to form good code per the use case.

This pushes the "what is best" to a narrower set of conditions where it can be better assessed - per function. In the growing by 2x case, realloc() is fine.

Example:

// Optimize for a growing allocation
// Return new pointer.
// Allocate per 2x *nmemb * size.
// Update *nmemb_new as needed.
// A return of NULL implies failure, old not deallocated.
void *my_alloc_grow(void *ptr, size_t *nmemb, size_t size) {
  if (nmemb == NULL) {
    return NULL;
  }
  size_t nmemb_old = *nmemb;

  if (size == 0) { // Consider array elements of size 0 as error
    return NULL;
  }
  if (nmemb_old > SIZE_MAX/2/size)) {
    return NULL;
  }

  size_t nmemb_new = nmemb_old ? (nmemb_old * 2) : 1;
  unsigned char *ptr_new = realloc(ptr, nmemb_new * size);
  if (ptr_new == NULL) {
    return NULL;
  }

  // Maybe zero fill new memory portion.
  memset(ptr_new + nmemb_old * size, 0, (nmemb_new - nmemb_old) * size);

  *nmemb = nmemb_new;
  return ptr_new;
}

Other use cases.

/ General new memory
void *my_alloc(size_t *nmemb, size_t size); // General new memory
void *my_calloc(size_t *nmemb, size_t size); // General new memory with zeroing

// General reallocation, maybe more or less.
// Act like free() on nmemb_new == 0.
void *my_alloc_resize(void *ptr, size_t *nmemb, size_t nmemb_new, size_t size);
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256