Array of POINTERS to Multiple Types, C
Asked Answered
T

7

14

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.

Tenpins answered 17/10, 2011 at 19:14 Comment(4)
Many things are possible, but many aren't a good idea. Use structs and unions (the latter ideally tagged with an enum so you know which field to access).Dulce
Aside from being a bad idea, how can it be done?Tenpins
What do you mean by "tagged with an enum"?Tenpins
There is a notion of a 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).Dulce
L
28

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);
Lan answered 17/10, 2011 at 21:3 Comment(4)
when i try to print the values they're...oddTenpins
If it's the char *, it just needs the cast without the extra dereference. I'll edit to add examples of printing the values.Lan
it's the int and the double that have issues. the string was fine!Tenpins
maybe there was a missing dereference for those,then; regardless, the update should show how to access them.Lan
E
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] );

}
Ellieellinger answered 17/10, 2011 at 19:28 Comment(5)
I get this... difftypes.c:10: warning: incompatible implicit declaration of built-in function ‘strncpy’Tenpins
oh you forgot to include string.hTenpins
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 freeing the memory it points to.Ellieellinger
Alright! Solid answer :) But you forgot to include string.hTenpins
gcc didn't give me the warning that you got. These things vary a bit between compilers.Ellieellinger
M
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.

Microwatt answered 17/10, 2011 at 19:16 Comment(6)
Why do they have to be the same type if you can make room using malloc?Tenpins
I can has short explanation? :-/Tenpins
When you say a[6] the compiler goes to the address a + sizeof(*a) * 6. That's why all the elements need to be the same size.Microwatt
@tekknolagi: when you reference an element in an array, it calculates the position based on the size of the elements. a[1] is a+1*sizeof(void). sizeof(void) isn't allowed, so it can't calculate where a[1] or any other index is.Armhole
@Tenpins You rushed a bit accepting this answer. Why don't you wait maybe better stuff comes up ?Microwatt
i mean... @Microwatt that was very honest... forgot to tag :PTenpins
C
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.

Catafalque answered 17/10, 2011 at 19:32 Comment(0)
C
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;
Crosley answered 17/10, 2011 at 19:16 Comment(2)
You probably meant *pIntTemp = 4.Microwatt
@Microwatt yes indeed. Good catch. FixedCrosley
M
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.

Mutazilite answered 28/12, 2018 at 0:57 Comment(0)
S
0
#include<stdlib.h>
#define int(x) *((int*)x)
#define float(x) *((float*)x)
#define double(x) *((float*)x)
#define char(x) x // for consistency
#define add_v(x, y) x = malloc(sizeof(y)); x = &y // Add void
#define add_vc(x, y) x = y  //Add void char
#define cl_v(x) free(&x) // Clear void
#define cl_vc(x) free((char)x)  // Clear void char

int main() {
    int n = 4;
    void *a[n];
    enum {num, name, pay, payment};
    int k = 5;
    add_v(a[num], k);
    char *b = "John";
    add_vc(a[name], b);
    float p = 13.45;
    add_v(a[pay], p);
    double o = 13054.5676;
    add_v(a[payment], o);

    printf("%d\n", int(a[num]));
    printf("%s\n", char(a[name]));
    printf("%f\n", float(a[pay]));
    printf("%f\n", double(a[payment]));

    cl_v(int(a[num]));
    cl_vc(char(a[name]));
    cl_v(float(a[pay]));
    cl_v(double(a[payment]));
}

Now it looks less messy. I used an enum to keep track of variable names.

If any macros are wrong, you can correct them, but the syntax looks fine.

Saturday answered 28/11, 2023 at 18:28 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.