Is there a way to change an array size using malloc and free functions only?

750 Views Asked by At

As homework, I have to write a function that changes an array's size using malloc and free functions only.

I know how to do it with realloc, but I don't know how to do it with malloc.

typedef struct{
    int *tab;
    int size;
}arr;

and I need to write this function :

void changeSizeDyn(arr *Dyn_arr, int size){
    //
}

As mentioned in the homework: it requires only one reallocation and only one memory release, and only one copy of the elements from the old array. I searched a lot but only found results using realloc.

Is it even possible to do that without using realloc?

5

There are 5 best solutions below

5
alx - recommends codidact On BEST ANSWER
struct Arr {
        int *tab;
        ptrdiff_t nmemb;
};

Let's say you allocated the array like this initially:

struct Arr x;

x.nmemb = 7;
x.tab = malloc(sizeof(*x.tab) * x.nmemb);

You should realloc it with this function:

void change_size_dyn(struct Arr *dyn_arr, ptrdiff_t nmemb)
{
        struct Arr old;
        ptrdiff_t cp_nmemb;

        if (!dyn_arr)
                return;
        old = *dyn_arr;

        if (nmemb <= 0)
                goto err;
        dyn_arr->tab = malloc(sizeof(*dyn_arr->tab) * nmemb);
        dyn_arr->nmemb = nmemb;

        cp_sz = MIN(old.nmemb, nmemb);
        memcpy(dyn_arr->tab, old.tab, cp_nmemb);
        free(old.tab);

        return;
err:
        dyn_arr->tab = NULL;
        dyn_arr->nmemb = 0;
        free(old.tab);
}

Which should be called this way:

change_size_dyn(&x, 9);

You can use this function even for the first allocation (although you should set to NULL and 0 both values first). You also don't need to add a free; an input of 0 would do it for you, just like realloc would do:

int main(void)
{
        struct Arr x = {0};

        change_size_dyn(&x, 5);
        /* ... */
        change_size_dyn(&x, 9);
        /* ... */

cleanup:
        change_size_dyn(&x, 0);
        return 0;
}

If you pass a structure where dyn_arr->nmemb is already a negative value (shouldn't happen), the behaviour is undefined (that negative value would go into memcpy, which would be wrapped into a very high size_t, which would overflow the arrays). I didn't want to check for that because it would be unnecessary in any non-buggy scenario.

1
Cheatah On
  1. Allocate memory of the new size using malloc.
  2. Copy all bytes from the old memory to the new memory. You can do that in a loop, you don't actually need to use functions for this (if that is the assignment).
  3. Free the old memory.
6
0___________ On
void *onlymallocrealloc(void *ptr, size_t oldsize, size_t newsize)
{
    void *newmem = malloc(newsize);

    if(ptr && newmem)
    {
        memcpy(newmem, ptr, oldsize);
        free(ptr);
    }
    return newmem;
}

and for your type (slightly changed)

typedef struct{
    int size;
    int tab[];
}arr;


arr *onlymalloc(arr *ptr, size_t newsize)
{
    ptr = onlymallocrealloc(ptr, ptr -> size * sizoef(ptr -> tab[0]) + sizeof(*ptr), newsize * sizoef(ptr -> tab[0]) + sizeof(*ptr));

    if(ptr)
    {
        ptr -> size = newsize;
    }
    return ptr;
}

edit if the function has to be void

void onlymallocrealloc(void **ptr, size_t oldsize, size_t newsize)
{
    void *newmem = malloc(newsize);

    if(*ptr && newmem)
    {
        memcpy(newmem, *ptr, oldsize);
        free(*ptr);
        *ptr = newmem;
    }
}

void onlymalloc(arr **ptr, size_t newsize)
{
    onlymallocrealloc(&ptr, *ptr -> size * sizoef(*ptr -> tab[0]) + sizeof(**ptr), newsize * sizoef(*ptr -> tab[0]) + sizeof(**ptr));

    if(*ptr)
    {
        *ptr -> size = newsize;
    }
}
3
Vlad from Moscow On

The function is not simple as it seems at first glance.

Here you are.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

typedef struct
{
    int *tab;
    int size;
} arr;

int changeSizeDyn( arr *Dyn_arr, int size )
{
    int success = 1;

    if ( Dyn_arr->size != size )
    {
        int *tmp = NULL;

        if ( size != 0 )
        {
            tmp = malloc( size * sizeof( int ) );
            success = tmp != NULL;

            if ( success )
            {
                int n = 0;

                if ( Dyn_arr->size != 0 )
                {
                    n = size < Dyn_arr->size ? size : Dyn_arr->size;

                    memcpy( tmp, Dyn_arr->tab, n * sizeof( int ) );
                }

                if ( n < size ) memset( tmp + n, 0, ( size - n ) * sizeof( int ) );
            }
        }

        if ( success )
        {
            free( Dyn_arr->tab );

            Dyn_arr->tab = tmp;
            Dyn_arr->size = size;
        }
    }

    return success;
}

int main( void )
{
    arr a = { NULL, 0 };
    int size = 10;

    if ( changeSizeDyn( &a, size ) )
    {
        for ( int i = 0; i < size; i++ ) a.tab[i] = i;

        for ( int i = 0; i < size; i++ ) printf( "%d ", a.tab[i] );
        putchar( '\n' );
    }

    size = 5;

    if ( changeSizeDyn( &a, size ) )
    {
        for ( int i = 0; i < size; i++ ) a.tab[i] = i;

        for ( int i = 0; i < size; i++ ) printf( "%d ", a.tab[i] );
        putchar( '\n' );
    }

    size = 0;

    if ( changeSizeDyn( &a, size ) )
    {
        for ( int i = 0; i < size; i++ ) a.tab[i] = i;

        for ( int i = 0; i < size; i++ ) printf( "%d ", a.tab[i] );
        putchar( '\n' );
    }

    assert( a.tab == NULL && a.size == 0 );
}

The program output is

0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 

The function has a return type int to signal whether its call was successful or not. You need a method to determine whether a function call was successful. Otherwise it will be impossible to determine this. So to use the type void as the return type of the function is a bad idea.

The function by default sets all new elements that do not correspond to the elements of the old array to zero. In fact it is not necessary but makes the usage of the structure more clear.

If the user passes the size equal to 0 then the function just free the old array and sets the data member tab to NULL.

If the new size and old size of the array are equal each other the function does nothing.

Take into account that it is much better to declare the data member size of the structure as having type size_t. Otherwise you need to add a check in the function that size is not negative. I did not do that because I suppose that you will indeed use the type size_t instead of the type int in the structure (and function) declaration.

1
alk On

Is it even possible to do that without using realloc?

Sure it is.

You could for example do it like this:

#include <stdlib.h> /* for malloc () and free () and EXIT_xxx macros. */
#include <stdio.h> /* for perror */

typedef struct{
  int *tab;
  size_t size; /* Prefer using size_t for (memory) sizes over using a 
                  signed int. */
} arr;

void changeSizeDyn(arr *parr, size_t size)
{
  if (!parr && !parr->tab)
  {
    errno = EINVAL;
  }
  else
  {
    arr tmp = {
      malloc(size * sizeof *tmp.tab),
      size
    };

    if (tmp.size && !tmp.tab)
    {
      perror("malloc() failed");
    }
    else
    {      
      for (
        size_t m = (tmp.size < parr->size) ?tmp.size :parr->size, i = 0;
        i < m; 
        ++i)
      {
        tmp.tab[i] = parr->tab[i];
      }

      free(parr->tab);

      *parr = tmp;

      errno = 0;
    }
  }
}

/* A main() to test the above: */
int main(void)
{
  const size_t s = 42;
  arr a = {
    malloc(s * sizeof *a.tab),
    s
  };

  if (a.size && !a.tab)
  {
    perror("malloc() failed");
    exit(EXIT_FAILURE);
  }

  /* populate a.tab[0] to a.tab[a.size - 1] here. */

  changeSizeDyn(&a, 43);
  if (0 != errno)
  {
    perror("changeSizeDyn() failed");
    exit(EXIT_FAILURE);
  }

  /* Use a.tab[0] to a.tab[a.size - 1] here. */

  free(a.tab);
  a.size = 0;
}