13

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.

tekknolagi
  • 10,663
  • 24
  • 75
  • 119
  • 2
    Many things are possible, but many aren't a good idea. Use `struct`s and `union`s (the latter ideally tagged with an `enum` so you know which field to access). –  Oct 17 '11 at 19:17
  • Aside from being a bad idea, how can it be done? – tekknolagi Oct 17 '11 at 19:18
  • What do you mean by "tagged with an enum"? – tekknolagi Oct 17 '11 at 19:22
  • 1
    There is a notion of a [tagged union](http://en.wikipedia.org/wiki/Tagged_union), in which an external value (the tag) tells you which part of the union is currently in use. If you have neither that nor another reliable way to tell which part is in use, you're headed for undefined behaviour (or whatever the standard says may happen when you write to field A but read as if field B was set). –  Oct 17 '11 at 19:27

6 Answers6

25

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);
Dmitri
  • 9,175
  • 2
  • 27
  • 34
4

You can easily have an array of pointers that point to different types. Of course for it to be very useful, you'd need to have some way of recording or determining what type is currently referenced by each element.

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

int main() {

    // This implicitly allocates memory to store 10 pointers
    void *a[10];

    // The first element will be a pointer to an int
    // Allocate the memory it points to, then assign it a value.
    a[0] = malloc(sizeof(int));
    *( (int *)a[0] ) = 4;

    // The second element will be a pointer to char; for simplicity,
    // I'm hardcoding the length of the string + 1 for the null byte.
    a[1] = malloc( 6*sizeof(char) );
    strncpy( a[1], "hello", 5 );

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

}
tekknolagi
  • 10,663
  • 24
  • 75
  • 119
Dave Costa
  • 47,262
  • 8
  • 56
  • 72
  • I get this... `difftypes.c:10: warning: incompatible implicit declaration of built-in function ‘strncpy’` – tekknolagi Oct 17 '11 at 19:39
  • oh you forgot to include `string.h` – tekknolagi Oct 17 '11 at 19:42
  • I used `strncpy` to put the string data into the memory block that is allocated by the `malloc` call. In the modified code in your question, you are allocating memory but then overwriting the pointer value, so you have a memory leak. You should either do it the way I have, or simply do `a[1] = "..."`. I chose to use dynamically allocated memory for each element for consistency. In any case, never do something like `ptr = malloc(...);` and then assign a new value to `ptr` without first `free`ing the memory it points to. – Dave Costa Oct 17 '11 at 20:07
  • Alright! Solid answer :) But you forgot to include `string.h` – tekknolagi Oct 17 '11 at 20:11
  • `gcc` didn't give me the warning that you got. These things vary a bit between compilers. – Dave Costa Oct 17 '11 at 20:47
3

No, all the elements have to be of the same type. You might get away with an array of structures.

struct mixed {
    enum {
        INTEGER,
        STRING,
    } type;
    union {
        int num;
        char *str;
    } value;
};


struct mixed v[10];
v[0].type = INTEGER;
v[0].value.num = 10;

I myself would never do such a thing (seems messy). But your array-of-void* approach is similar: you have to store the information on the type somewhere.

cnicutar
  • 178,505
  • 25
  • 365
  • 392
1

I'm not sure what you want to achieve but there are two possibilities:

1 - You don't actually want an array but a struct:

struct {
    int number;
    char *string;
} a;

In this case you can access the number as a.number and the string as a.string.

2 - You want an array of variant type. In C, you can use unions (preferably tagged) for variant types:

struct Variant {
    int type;
    union {
        int number;
        char *string;
    }
}

Then you can encode your type with 0 for number and 1 for string. Using an enum instead of integer for the type would be a better way of course.

cyco130
  • 4,654
  • 25
  • 34
0

The bigest issue is getting the C compiler to treat each element of the array differently.

Might I suggest a hybrid approach.

Set aside several pointers, each with their appropriate structure definitions.

When you decide which kind of element you want, use that pointer to malloc and setup, then later use.

Then copy the value of that pointer into the array of pointers.

Later, when you want to use that element, copy the array element into it's aproprate pointer to make the compiler happy.

Please keep in mind, this is only an example, it has some short commings like difficulty of sorting or inserting a node in the middle, but...

For example:

struct      this_type {
    char        mod_kind[20];
    int         this_int;
};
struct      that_type {
    char        mod_kind[20];
    char        that_string[20];
};

void  *list_o_pointers[10];
struct  this_type       *p_this;
struct  that_type       *p_that;

p_this = malloc(sizeof(struct this_type));
list_o_pointers[0] = p_this;
strcpy(p_this->mod_kind, "this kind");  // or whatever you want to use to differentate different types

p_that = malloc(sizeof(struct that_type));
list_o_pointers[0] = p_that;
strcpy(p_that->mod_kind, "that kind");

// later
p_this = list_o_pointers[0];
p_that = list_o_pointers[0];
if (strstr(p_this->mod_kind, "this kind")) { /* do this stuff */ }
if (strstr(p_that->mod_kind, "that kind")) { /* do that stuff */}

it solves the ulgyness of stuff like having to cast *((double *)(arr[2].data)) = and also helps with readability.

This may break down if you have many different node structures.

It is a bit brute force, but (IMHO) it's a little easier on the brain. The array is a simple array and each node is simple. The nodes have no need for a "next" pointer like a linked list has.

Mark.

Cool Javelin
  • 776
  • 2
  • 10
  • 26
0

It's because you're trying to store a value into a slot which is expecting a pointer. Try the following (error checking omitted for brevity)

int* pIntTemp = malloc(sizeof(int));
*pIntTemp = 4;
a[0] = pIntTemp;
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454