cstructuredynamic-memory-allocation

Structure - Unknown Size


I'm currently trying to allocate a structure of unknown size. I know the below works, but my question is how can I perform the below, if I don't know the size of "num" in advance? When I try to allocate *num directly with malloc and a size of 2, it doesn't let me access num[0] and num[1], like I've been able to do with the int array pointers.

I know I can do the above if I'm not using a structure. For example, int *A with malloc(10*(sizeof(int *)), lets me access a[0], along with a[5]. With structures, it doesn't seem like I'm able to do this, but I'm probably just doing something wrong.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
typedef struct
{
char word[50];
} test;

int main(void)
{
    test *num[2];
    num[0]=(test *)malloc(3*sizeof(test *));
    num[1]=(test *)malloc(3*sizeof(test *));
    strcpy(num[0]->word, "hello");
    strcpy(num[1]->word, "... you");
    puts(num[0]->word);
    puts(num[1]->word);
}

Solution

  • Generally:

    sizeof (test *) (the size of a pointer) is not guaranteed to be the same as (i.e., here, surely less than) sizeof (test) (what that pointer points to; here, the size of a structure).

    On my machine

    #include <stdio.h>
    
    typedef struct {
        char word[50];
    } test;
    
    int main(void)
    {
        printf("sizeof (test) = %zu\n", sizeof (test));
        printf("sizeof (test *) = %zu\n", sizeof (test *));
    }
    

    produces

    sizeof (test) = 50
    sizeof (test *) = 8
    

    A short allocation means there will not be enough memory to represent the structure members, and undefined behaviour when you exhaust said memory. You appear to have experienced problems, and attempted to remedy them by tripling the size of each allocation, as shown in the example. This is still an incorrect amount of memory if you want pointers to three contiguous structures, but enough memory that copying a small string (e.g., strlen(str) < 3 * sizeof (test *)) to just the first of these structures is unlikely to cause issues (i.e., segfault).

    In your test with ints, the same mistake is unlikely to raise any eyebrows. Again, on my machine

    #include <stdio.h>
    
    int main(void)
    {
        printf("sizeof (int) = %zu\n", sizeof (int));
        printf("sizeof (int *) = %zu\n", sizeof (int *));
    }
    

    produces

    sizeof (int) = 4
    sizeof (int *) = 8
    

    Using sizeof (int *) bytes to store an int object is likely an over-allocation of memory at worst, maybe break-even. You can check your machine if you are curious, but it does not particularly matter - if you use sizeof on the correct expression, you will have the correct amount of memory.

    The general form is

    T *foo = malloc(sizeof *foo); // equivalent: sizeof (T)
    

    where you are allocating the number of bytes required to store the object itself (not the pointer to said object). This does not change if the pointer value will be assigned to an element in an array.

    T *bar[3];
    bar[0] = malloc(sizeof *bar[0]); // equivalent: sizeof (T)
    

    For the more specific question, which can be rephrased as

    How do I allocate an array which might need to change sizes?

    the answer is start with an optimistic or otherwise default length, and allocate enough memory with malloc (or calloc; or start with a NULL pointer value in the event that your starting length is zero). When space requirements change, resize the array with realloc.

    Here is an arbitrary example that grows an array, one element at a time.

    #include <stdio.h>
    #include <stdlib.h>
    
    struct line {
        char buf[64];
    };
    
    int main(void)
    {
        size_t length = 0;
        struct line *lines = NULL;
    
        struct line current;
    
        while (1) {
            printf("> ");
    
            if (!fgets(current.buf, sizeof current.buf, stdin))
                break;
    
            void *mem = realloc(lines, sizeof *lines * (length + 1));
    
            if (!mem) {
                fputs("Out of memory.\n", stderr);
                return 1;
            }
    
            lines = mem;
            lines[length++] = current;
        }
    
        putchar('\n');
    
        for (size_t i = 0; i < length; i++)
            fputs(lines[i].buf, stdout);
    
        free(lines);
    }
    
    > hello
    > world
    > 
    hello
    world