Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting structure pointers between structs containing pointers to different types?

I have a structure, defined by as follows:

struct vector
{
  (TYPE) *items;
  size_t nitems;
};

where type may literally be any type, and I have a type-agnostic structure of similar kind:

struct _vector_generic
{
  void *items;
  size_t nitems;
};

The second structure is used to pass structures of the first kind of any type to a resizing function, for example like this:

struct vector v;
vector_resize((_vector_generic*)&v, sizeof(*(v->items)), v->nitems + 1);

where vector_resize attempts to realloc memory for the given number of items in the vector.

int
vector_resize (struct _vector_generic *v, size_t item_size, size_t length)
{
  void *new = realloc(v->items, item_size * length);
  if (!new)
    return -1;

  v->items = new;
  v->nitems = length;

  return 0;
}

However, the C standard states that pointers to different types are not required to be of the same size.

6.2.5.27:

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.39) Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

Now my question is, should I be worried that this code may break on some architectures?

Can I fix this by reordering my structs such that the pointer type is at the end? for example:

struct vector
{
  size_t nitems;
  (TYPE) *items;
};

And if not, what can I do?

For reference of what I am trying to achieve, see:
https://github.com/andy-graprof/grapes/blob/master/grapes/vector.h

For example usage, see:
https://github.com/andy-graprof/grapes/blob/master/tests/grapes.tests/vector.exp

like image 627
Andreas Grapentin Avatar asked Sep 06 '25 13:09

Andreas Grapentin


1 Answers

You code is undefined.

Accessing an object using an lvalue of an incompatible type results in undefined behavior.

Standard defines this in:

6.5 p7:

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

— a type compatible with the effective type of the object,

— a qualified version of a type compatible with the effective type of the object,

— a type that is the signed or unsigned type corresponding to the effective type of the object,

— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,

— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or

— a character type.

struct vector and struct _vector_generic have incompatible types and do not fit into any of the above categories. Their internal representation is irrelevant in this case.

For example:

struct vector v;
_vector_generic* g = &v;
g->size = 123 ;   //undefined!

The same goes for you example where you pass the address of the struct vector to the function and interpret it as a _vector_generic pointer.

The sizes and padding of the structs could also be different causing elements to be positioned at different offsets.

What you can do is use your generic struct, and cast if depending on the type the void pointer holds in the main code.

struct gen
{
    void *items;
    size_t nitems;
    size_t nsize ;
};

struct gen* g = malloc( sizeof(*g) ) ;
g->nitems = 10 ;
g->nsize = sizeof( float ) ;
g->items = malloc( g->nsize * g->nitems ) ;
float* f = g->items ;
f[g->nitems-1] = 1.2345f ;
...

Using the same struct definition you can allocate for a different type:

struct gen* g = malloc( sizeof(*g) ) ;
g->nitems = 10 ;
g->nsize = sizeof( int ) ;
g->items = malloc( g->nsize * g->nitems ) ;
int* i = g->items ;
...

Since you are storing the size of the type and the number of elements, it is obvious how your resize function would look like( try it ).

You will have to be careful to remember what type is used in which variable as the compiler will not warn you because you are using void*.

like image 186
2501 Avatar answered Sep 09 '25 11:09

2501