Search code examples
cpointersmallocdynamic-arrays

Is it possible to create a dynamic array without explicitly defining a variable to specify the length?


I’m trying to create a dynamic array which can be modified using the functions Array_Push(array, val) & Array_Del(array, index). Now the current way I have this I need a variable to keep track of the size of it. My implementation of this concept is to store the data/size in a struct like so:

struct Array {  
    int *data;  
    int size;  
}

In order to read the actual array you have to type array.data[i] which I think is a little bit redundant.

My solution to this was attempting to store the size of the array in a different index. I didn’t want to store it inside [0] as that would create a lot of confusion, so I wanted to try storing it inside of [-1]. An obvious problem with this is that [-1] is outside the array. What I did instead was create an array via int *array = malloc(sizeof(int)). After that I increment the pointer to it via array += 1. However when I try to free it with free(array), I end up freeing non malloc()ed memory. My current thoughts are that the problem lies somewhere in that when I call malloc() inside a function it doesn't modify my original pointer. If this were the case then I was wondering how I could fix this so it does. Here is my code in case it helps:

array.h:

#pragma once

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

int *Array_Create() {
  int *array = malloc(sizeof(int));

  // If malloc fails to allocate memory
  if (array == NULL) {
    printf("Error creating array: malloc() failed to allocate memory");
    exit(12);
  }

  array[0] = 0;

  array += 1;

  return array;
}

void Array_Push(int *array, int value) {
  if (array[-1] == 0) {
    array = malloc(sizeof(int));
  } else {
    // array = realloc(array, sizeof(int) * array[-1]);
    printf("ERROR READING ARRAY SIZE IN: Array_Push()");
  }
}

main.c:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <dynamic_array.h>

int main() {
  int *array = Array_Create();

  printf("%d", array[-1]);

  Array_Push(array, 1);

  free((array - 1));
  free(array);

  return 0;
}

Solution

  • Your uses of malloc and free suggest you think individual elements of an array can be allocated and freed, or you have some other conceptual error about memory allocation.

    malloc gives you an address of block of memory, and you must pass that same address to free. You cannot pass any other address in that block to free. It is fine to add one to the address and use that for your indexing, but, when you pass an address to free, you must reconstruct the original address by subtracting one.

    Array_Create can be:

    int *Array_Create(void)
    {
        int *array = malloc(sizeof *array);
        if (!array)
        {
            fprintf(stderr, "Error creating array:  malloc failed to allocate memory.\n");
            exit(EXIT_FAILURE);
        }
        array[0] = 0;
        return array +1;
    }
    

    and Array_Push can be:

    int *Array_Push(int *array, int value)
    {
        int *temp = realloc(array-1, (array[-1] + 1) * sizeof *temp);
            // Note use of array-1 when passing address to realloc.
        if (!temp)
        {
            fprintf(stderr, "Error growing array:  realloc failed to allocate memory.\n");
            exit(EXIT_FAILURE);
        }
        array = temp+1;
        ++array[-1];
        array[0] = value;
        return array;
    }
    

    When you free the array, you should use only free(array-1), not free(array).

    Note that Array_Push needs to provide a new value for array, so I wrote it to return that value. When you call it, it should be called as array = Array_Push(array, value);. It could also be written to accept a pointer to array, as in void Array_Push(int **array, int value); and called as Array_Push(&array, value);. This requires corresponding changes to its code.

    That said, this sort of arrangement is avoided by good programmers, for reasons of design, engineering, and quality that are beyond the scope of this answer. This sort of code might be used in some situations, but you generally want to avoid kludges like this.