1

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?

Claudio
  • 10,614
  • 4
  • 31
  • 71
John
  • 21
  • 4
  • Just allocate a new array, copy the contents, and free the old one. That's what realloc does. – alx - recommends codidact May 12 '19 at 14:22
  • Well, probably the bits in an optimized c library are better than that. If contiguous memory is available, probably it won't touch the old contents, and just extend them. If there isn't, I guess it will do exactly that. But the concept is that. – alx - recommends codidact May 12 '19 at 14:29
  • @alk Derp. that makes considerable sense seeing the structure. Redacted, and thanks for keeping me honest. – WhozCraig May 12 '19 at 22:00

5 Answers5

5
  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.
Cheatah
  • 1,825
  • 2
  • 13
  • 21
  • Can you provide some code? Because I'm not sure I understand how this exactly works. – John May 12 '19 at 14:50
1

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.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • "*Otherwise it will be impossible to determine this*" Hm? One can fallback to using `errno`. – alk May 12 '19 at 15:42
  • @alk errno is a bad idea for using in a multithreading environment. – Vlad from Moscow May 16 '19 at 12:49
  • I assume it's save to use `errno` in a multithreaded environment at least for POSIX compliant systems. Also see this answer: https://stackoverflow.com/a/1694170/694576 Still, I'd agreed on, that doing so is not nice. – alk May 16 '19 at 15:09
1
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.

  • 2
    A function having the return type void may not return an expression of the value NULL. See your code if (!dyn_arr) return NULL; – Vlad from Moscow May 12 '19 at 15:08
  • Why the heck do you use `ptrdiff_t `? – alk May 12 '19 at 15:47
  • @alk Because it's what it represents, it is the pointer difference between one past the last element in the array and the first element. It's not a size in bytes, which is the only case I use `size_t`. So I use `size_t` for `unsigned char` arrays, where you mean the size in bytes (so that's for `memcpy` and it's family of functions almost exclusively), and `ptrdiff_t` for the rest of arrays. – alx - recommends codidact May 12 '19 at 15:50
  • 1
    I see. Interesting argumentation, indeed. – alk May 12 '19 at 15:55
  • @alk I renamed `size` to `nmemb` which makes more sense with `ptrdiff_t` :) – alx - recommends codidact May 12 '19 at 16:28
1

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;
}
alk
  • 69,737
  • 10
  • 105
  • 255
  • First time i saw initialization code like this,arr a = { malloc(s * sizeof *a.tab), s }; :) :) – BEPP May 12 '19 at 15:27
-2
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;
    }
}
alk
  • 69,737
  • 10
  • 105
  • 255
0___________
  • 60,014
  • 4
  • 34
  • 74