Structures and casting in C

前端 未结 8 1484
野性不改
野性不改 2020-12-16 02:07

I was wondering:

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

struct Base {
  int foo;
};

struct Derived {
  int foo; // int foo is comm         


        
相关标签:
8条回答
  • 2020-12-16 02:47

    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.

    0 讨论(0)
  • 2020-12-16 02:49

    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.

    0 讨论(0)
  • 2020-12-16 02:52

    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.

    0 讨论(0)
  • 2020-12-16 02:53

    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.

    0 讨论(0)
  • 2020-12-16 02:54

    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.

    0 讨论(0)
  • 2020-12-16 02:55

    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.

    0 讨论(0)
提交回复
热议问题