Search code examples
carrayspointersvoid

Array of POINTERS to Multiple Types, C


Is it possible to have an array of multiple types by using malloc?

EDIT:

Currently I have:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define int(x) *((int *) x)


int main() {
        void *a[10];

        a[0] = malloc(sizeof(int));
        int(a[0]) = 4;

        char *b = "yola.";

        a[1] = malloc(strlen(b)*sizeof(char));
        a[1] = b;

        printf("%d\n", int(a[0]));
        printf("%s\n", a[1]);
}

But it's messy. Other ways?

EDIT: Cleaned it up a bit.


Solution

  • You can't have an array of different types, exactly. But you can achieve a similar effect (for some purposes at least) in a number of different ways.

    If you just want a few values of different types packaged together, but the number and types of values don't change, you just need a struct and can access them by name:

    struct s_item {
      int     number;
      char    str[100];
    } item;
    item.number = 5;
    strcpy(item.str,"String less than 100 chars");
    

    If you know what types you might use, you can create a union, or a struct containing a union so you can tag it with the type. You can then create an array of those. The type member lets you check to see what you stored in each array element later.

    enum ElementType { et_str, et_int, et_dbl };
    struct Element {
      ElementType type;
      union {
        char      *str;
        int       i;
        double    d;
      }
    };
    
    struct Element *arr = malloc(sizeof(struct Element) * 3);
    arr[0].type = et_str;
    arr[0].str = strdup("String value"); /* remember to free arr[0].str */
    arr[1].type = et_int;
    arr[1].i = 5;
    arr[2].type = et_dbl;
    arr[2].d = 27.3;
    
    /* access the values.. */
    for (int i = 0; i < 3; i++) {
      switch(arr[i].type) {
        case et_str: printf("String: %s\n",arr[i].str); break;
        case et_int: printf("Integer: %d\n",arr[i].i); break;
        case et_dbl: printf("Double: %f\n",arr[i].d); break;
      }
    }
    
    /* The strings are dynamically allocated, so free the strings */
    for (int i = 0; i < 3; i++)
      if (arr[0].type == et_str) free(arr[0].str);
    /* free the malloc'ed array */
    free(arr);
    /* etc., etc. */
    

    This approach may waste space because:

    • Each element has an extra value to keep track of the type of data it holds
    • The struct may have extra padding between its members
    • The types in the union may be different sizes, in which case the union will be as large as the largest type

    If you have another way of knowing what type you've stored in each element, you can use just the bare union without the struct wrapping it. This is a little more compact, but each element will still be at least as large as the largest type in the union.


    You can also create an array of void * values. If you do this, you'll have to allocate the items somehow and assign their addresses to the array elements. Then you'll need to cast them to the appropriate pointer type to access the items. C doesn't provide any runtime type information, so there's no way to find out what type of data each element points at from the pointer itself -- you must keep track of that on your own. This approach is a lot more compact than the others when the types you're storing are large and their sizes vary a lot, since each is allocated separately from the array and can be given only the space needed for that type. For simple types, you don't really gain anything over using a union.

    void **arr = malloc(3 * sizeof(void *));
    arr[0] = strdup("Some string"); /* is a pointer already */
    arr[1] = malloc(sizeof(int));
    *((int *)(arr[1])) = 5;
    arr[2] = malloc(sizeof(double));
    *((double *)(arr[2])) = 27.3;
    
    /* access the values.. */
    printf( "String: %s\n", (char *)(arr[0]) );
    printf( "Integer: %d\n", *((int *)(arr[1])) );
    printf( "Double: %f\n", *((double *)(arr[2])) );
    
    /* ALL values were dynamically allocated, so we free every one */
    for (int i = 0; i < 3; i++)
      free(arr[i]);
    /* free the malloc'ed array */
    free(arr);
    

    If you need to keep track of the type in the array, you can also use a struct to store the type along with the pointer, similar to the earlier example with the union. This, again, is only really useful when the types being stored are large and vary a lot in size.

    enum ElementType { et_str, et_int, et_dbl };
    struct Element {
      ElementType type;
      void        *data;
    };
    
    struct Element *arr = malloc(sizeof(struct Element) * 3);
    arr[0].type = et_str;
    arr[0].data = strdup("String value");
    arr[1].type = et_int;
    arr[1].data = malloc(sizeof(int));
    *((int *)(arr[1].data)) = 5;
    arr[2].type = et_dbl;
    arr[2].data = malloc(sizeof(double));
    *((double *)(arr[2].data)) = 27.3;
    
    /* access the values.. */
    for (int i = 0; i < 3; i++) {
      switch(arr[i].type) {
        case et_str: printf( "String: %s\n", (char *)(arr[0].data) ); break;
        case et_int: printf( "Integer: %d\n", *((int *)(arr[1].data)) ); break;
        case et_dbl: printf( "Double: %f\n", *((double *)(arr[2].data)) ); break;
      }
    }
    
    /* again, ALL data was dynamically allocated, so free each item's data */
    for (int i = 0; i < 3; i++)
      free(arr[i].data);
    /* then free the malloc'ed array */
    free(arr);