11

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 *?

ib.
  • 27,830
  • 11
  • 80
  • 100
Anon
  • 113
  • 4
  • 3
    Did you try it ? What happened ? – Paul R Oct 02 '10 at 16:19
  • 5
    It worked without the compiler complaining, but I'd like to know whether this is a commonly usable practice, or some nasty hack (it would help me a ton right now). – Anon Oct 02 '10 at 16:23
  • 3
    @PaulR if your question meant to say "try yourself instead of asking", you should learn about Undefined Behavior and how it can make your experiment look correct today but fail tomorrow - and viceversa. – hmijail Feb 06 '17 at 20:08

8 Answers8

11

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.

Christoph
  • 164,997
  • 36
  • 182
  • 240
8

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.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/106141/discussion-on-answer-by-zwol-structures-and-casting-in-c). – George Stocker Mar 13 '16 at 13:30
  • 1
    @GeorgeStocker I happen not to care so much in this case because it wasn't _me_ having an extended discussion in the comments, but it's the first time this has happened to one of my Stack Overflow answers, so you are hereby notified that I do not want moderators to migrate or delete *any* comments on my questions and answers, nor to migrate or delete *any* of my comments on anyone's questions or answers, under any circumstances except for actual spam and abuse. The current policy is wrong and is actively harmful to the site. – zwol Mar 14 '16 at 18:59
1

In particular cases this could work, but in general - no, because of the structure alignment.

You could use different #pragmas to make (actually, attempt to) the alignment identical - and then, yes, that would work.

If you're using microsoft visual studio, you might find this article useful.

Yippie-Ki-Yay
  • 22,026
  • 26
  • 90
  • 148
1

That will work in this particular case. The foo field in the first member of both structures and hit has the same type. However this is not true in the general case of fields within a struct (that are not the first member). Items like alignment and packing can make this break in subtle ways.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
1

As you seem to be aiming at Object Oriented Programming in C I can suggest you to have a look at the following link:

http://www.planetpdf.com/codecuts/pdfs/ooc.pdf

It goes into detail about ways of handling oop principles in ANSI C.

gilligan
  • 488
  • 3
  • 15
  • Thanks, that seems to be the first resource I'm consulting that is actually useful :) – Anon Oct 02 '10 at 16:44
1

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

gilligan
  • 488
  • 3
  • 15
0

I know this is an old question, but in my view there is more that can be said and some of the other answers are incorrect.

Firstly, this cast:

(struct Base *)ptr

... is allowed, but only if the alignment requirements are met. On many compilers your two structures will have the same alignment requirements, and it's easy to verify in any case. If you get past this hurdle, the next is that the result of the cast is mostly unspecified - that is, there's no requirement in the C standard that the pointer once cast still refers to the same object (only after casting it back to the original type will it necessarily do so).

However, in practice, compilers for common systems usually make the result of a pointer cast refer to the same object.

(Pointer casts are covered in section 6.3.2.3 of both the C99 standard and the more recent C11 standard. The rules are essentially the same in both, I believe).

Finally, you've got the so called "strict aliasing" rules to contend with (C99/C11 6.5 paragraph 7); basically, you are not allowed to access an object of one type via a pointer of another type (with certain exceptions, which don't apply in your example). See "What is the strict-aliasing rule?", or for a very in-depth discussion, read my blog post on the subject.

In conclusion, what you attempt in your code is not guaranteed to work. It might be guaranteed to always work with certain compilers (and with certain compiler options), and it might work by chance with many compilers, but it certainly invokes undefined behavior according to the C language standard.

What you could do instead is this:

*((int *)ptr) = 1;

... I.e. since you know that the first member of the structure is an int, you just cast directly to int, which bypasses the aliasing problem since both types of struct do in fact contain an int at this address. You are relying on knowing the struct layout that the compiler will use and you are still relying on the non-standard semantics of pointer casting, but in practice this is significantly less likely you give you problems.

Community
  • 1
  • 1
davmac
  • 20,150
  • 1
  • 40
  • 68
  • "other answers are incorrect" -> which ones? And how exactly does your answer differ from them? – hmijail Apr 24 '17 at 09:51
  • @hmijail (a) all of them, except that the accepted answer has been edited since and (b) if you read my answer and the other answers, the difference should be obvious enough. One of them claims that structure alignment will cause a problem (in practice it usually won't) but in fact the pointer cast has an unspecified result (in practice this doesn't matter). Two of them claim OP's code "will work". The one by Christoph I agree with (may have missed that first time around). Others don't provide a direct answer. Anyway I'll edit to remove the blanket statement. – davmac Apr 24 '17 at 10:33
  • @hmijail btw the accepted answer was modified due to my own comments, see the [discussion](http://chat.stackoverflow.com/rooms/106141/discussion-on-answer-by-zwol-structures-and-casting-in-c). Incidentally what most differentiates my answer from the others is that it actually discusses the strict aliasing rule. – davmac Apr 24 '17 at 10:53
-1

The great/bad thing about C is that you can cast just about anything -- the problem is, it might not work. :) However, in your case, it will*, since you have two structs whose first members are both of the same type; see this program for an example. Now, if struct derived had a different type as its first element -- for example, char *bar -- then no, you'd get weird behavior.

* I should qualitfy that with "almost always", I suppose; there're a lot of different C compilers out there, so some may have different behavior. However, I know it'll work in GCC.

mipadi
  • 398,885
  • 90
  • 523
  • 479
  • "Almost always" and citing a program as an example is a recipe for disaster when talking about C and UB-related problems. – hmijail Apr 24 '17 at 09:48