Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to cast pointers from a structure type to another structure type extending the first in C?

If I have structure definitions, for example, like these:

struct Base {
  int foo;
};

struct Derived {
  int foo; // int foo is common for both definitions
  char *bar;
};

Can I do something like this?

void foobar(void *ptr) {
  ((struct Base *)ptr)->foo = 1;
}

struct Derived s;

foobar(&s);

In other words, can I cast the void pointer to Base * to access its foo member when its type is actually Derived *?

like image 367
Anon Avatar asked Oct 02 '10 16:10

Anon


3 Answers

There is another little thing that might be helpful or related to what you are doing ..

#define SHARED_DATA int id;

typedef union base_t {
    SHARED_DATA;
    window_t win;
    list_t   list;
    button_t button;         
}

typedef struct window_t {
    SHARED_DATA;
    int something;
    void* blah;
}

typedef struct window_t {
    SHARED_DATA;
    int size;
 }

typedef struct button_t {
    SHARED_DATA;
    int clicked;
 }

Now you can put the shared properties into SHARED_DATA and handle the different types via the "superclass" packed into the union.. You could use SHARED_DATA to store just a 'class identifier' or store a pointer.. Either way it turned out handy for generic handling of event types for me at some point. Hope i'm not going too much off-topic with this

like image 183
gilligan Avatar answered Oct 06 '22 01:10

gilligan


You should do

struct Base {
  int foo;
};

struct Derived {
  struct Base base;
  char *bar;
};

to avoid breaking strict aliasing; it is a common misconception that C allows arbitrary casts of pointer types: although it will work as expected in most implementations, it's non-standard.

This also avoids any alignment incompatibilities due to usage of pragma directives.

like image 11
Christoph Avatar answered Nov 09 '22 12:11

Christoph


Many real-world C programs assume the construct you show is safe, and there is an interpretation of the C standard (specifically, of the "common initial sequence" rule, C99 §6.5.2.3 p5) under which it is conforming. Unfortunately, in the five years since I originally answered this question, all the compilers I can easily get at (viz. GCC and Clang) have converged on a different, narrower interpretation of the common initial sequence rule, under which the construct you show provokes undefined behavior. Concretely, experiment with this program:

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

typedef struct A { int x; int y; }          A;
typedef struct B { int x; int y; float z; } B;
typedef struct C { A a;          float z; } C;

int testAB(A *a, B *b)
{
  b->x = 1;
  a->x = 2;
  return b->x;
}

int testAC(A *a, C *c)
{
  c->a.x = 1;
  a->x = 2;
  return c->a.x;
}

int main(void)
{
  B bee;
  C cee;
  int r;

  memset(&bee, 0, sizeof bee);
  memset(&cee, 0, sizeof cee);

  r = testAB((A *)&bee, &bee);
  printf("testAB: r=%d bee.x=%d\n", r, bee.x);

  r = testAC(&cee.a, &cee);
  printf("testAC: r=%d cee.x=%d\n", r, cee.a.x);

  return 0;
}

When compiling with optimization enabled (and without -fno-strict-aliasing), both GCC and Clang will assume that the two pointer arguments to testAB cannot point to the same object, so I get output like

testAB: r=1 bee.x=2
testAC: r=2 cee.x=2

They do not make that assumption for testAC, but — having previously been under the impression that testAB was required to be compiled as if its two arguments could point to the same object — I am no longer confident enough in my own understanding of the standard to say whether or not that is guaranteed to keep working.

like image 8
zwol Avatar answered Nov 09 '22 11:11

zwol