4

I'm writing a C program in which I define two types:

typedef struct {

    uint8_t array[32];
    /* struct A's members */
    ...

} A;

typedef struct {

    uint8_t array[32];
    /* struct B's members, different from A's */
    ...

} B;

Now I would like to build a data structure which is capable of managing both types without having to write one for type A and one for type B, assuming that both have a uint8_t [32] as their first member.

I read how to implement a sort of polymorphism in C here and I also read here that the order of struct members is guaranteed to be kept by the compiler as written by the programmer.

I came up with the following idea, what if I define the following structure:

typedef struct {
    uint8_t array[32];
} Element;

and define a data structure which only deals with data that have type Element? Would it be safe to do something like:

void f(Element * e){
    int i;
    for(i = 0; i < 32; i++) do_something(e->array[i]);
}

...

A a;
B b;
...
f(((Element *)&a));
...
f(((Element *)&b));

At a first glance it looks unclean, but I was wondering whether there are any guarantees that it will not break?

Community
  • 1
  • 1
Simone Bronzini
  • 1,057
  • 1
  • 12
  • 23
  • Is there some reason you're not using C++, if what you want is a polymorphic structure? The expression "use the right tool for the job" comes to mind. – Eric Lippert Apr 03 '14 at 21:28
  • What's the signature of `do_something()`? Is it limited to accessing only one byte or the array? – Arun Apr 03 '14 at 21:30
  • @EricLippert You're right, but I've never used C++ before and this code is part of a project I started long ago using C without having a clear idea of the long term problems I would have had to address. – Simone Bronzini Apr 03 '14 at 21:30
  • @Arun: it's a placeholder I used in this example to mean a generic function that will access the array a byte at a time. – Simone Bronzini Apr 03 '14 at 21:32
  • 1
    Why not `A` and `B` have `Element` directly as their first member? – Arun Apr 03 '14 at 21:40
  • @Arun: yeah, that's definitely a possibility. But then my data structure holding (Element *) kind of objects would not hold pointers to As and Bs but only to their inner member Element. I would also like to understand what is wrong with the solution I proposed. – Simone Bronzini Apr 03 '14 at 21:43
  • 1
    Oh no, there is nothing wrong in your proposal. That would work fine. It is more about understanding your goal and compare to other possibilities. – Arun Apr 03 '14 at 21:52
  • You should be fine with your approach as long as you don't overflow `array`. Another thing is that you don't really need to define `Element` because `(uint8_t*)` &a also works. – Heeryu Apr 04 '14 at 03:40
  • This is commonly used in Linux source for e.g. linked lists. – Dima Tisnek May 14 '15 at 13:17

3 Answers3

1

If array is always the first in your struct, you can simply access it by casting pointers. There is no need for a struct Element. You data structure can store void pointers.

typedef struct {
    char array[32];
} A;

typedef struct {
    void* elements;
    size_t elementSize;
    size_t num;
} Vector;

char* getArrayPtr(Vector* v, int i) {
    return (char*)(v->elements) + v->elementSize*i;
}

int main()
{
    A* pa = malloc(10*sizeof(A));
    pa[3].array[0] = 's';
    Vector v;
    v.elements = pa;
    v.num = 10;
    v.elementSize = sizeof(A);
    printf("%s\n", getArrayPtr(&v, 3));
}
Danvil
  • 22,240
  • 19
  • 65
  • 88
0

but why not have a function that works with the array directly void f(uint8_t array[32]){ int i; for(i = 0; i < 32; i++) do_something(array[i]); }

and call it like this f(a.array) f(b.array)

polymorphism makes sense when you want to kepp a and b in a container of some sorts and you want to iterate over them but you dont want to care that they are different types.

Mihai Sebea
  • 398
  • 2
  • 10
  • Sorry, maybe I was not clear enough in my question. I need a data structure which serves as a container for any kind of type which has a uint8_t [32] array as its first member. The data structure I propose will store its elements as (Element *) kind of objects. – Simone Bronzini Apr 03 '14 at 21:35
  • Seems to me that what you are trying to do is use C but still keep some c++ type safenes... Since this is C you could store your objects as void* and since you know that they start with uint8_t array[32] cast to that ... or you could access the memory directly ... nobody will mind or care ... in the end it's only memory – Mihai Sebea Apr 03 '14 at 21:40
  • yeah, that's the point, am I guaranteed that accessing `((Element *)&a)->array` will always be the same as accessing `a.array`? – Simone Bronzini Apr 03 '14 at 21:47
0

This should work fine if you, you know, don't make any mistakes. A pointer to the A struct can be cast to a pointer to the element struct, and so long as they have a common prefix, access to the common members will work just fine.

A pointer to the A struct, which is then cast to a pointer to the element struct can also be cast back to a pointer to the A struct without any problems. If element struct was not originally an A struct, then casting the pointer back to A will be undefined behavior. And this you will need to manage manually.

One gotcha (that I've run into) is, gcc will also allow you to cast the struct back and forth (not just pointer to struct) and this is not supported by the C standard. It will appear to work fine until your (my) friend tries to port the code to a different compiler (suncc) at which point it will break. Or rather, it won't even compile.

luser droog
  • 18,988
  • 3
  • 53
  • 105