Inheritance and offsetof

Discussion in 'C++' started by Marcel Müller, Sep 21, 2009.

  1. When I switch an existing project to gcc I get dozens of warnings
    concerning offsetof. The code runs fine with and without gcc. Of course
    this means nothing.

    Example:


    #include <stddef.h>

    #define structoffsetof(type, base) ((int)(static_cast<base*>((type*)64))-64)

    struct A
    { int m1;
    int m2;
    };

    struct B : A
    { int m3;
    };

    int main()
    { A a;
    a.m1 = offsetof(B, m3) - structoffsetof(B, A);
    return 0;
    }


    test.cpp: In function `int main()':
    test.cpp:16: warning: invalid access to non-static data member `A::m1'
    of NULL object
    test.cpp:16: warning: (perhaps the `offsetof' macro was used incorrectly)


    Of course, it is because B is no longer a C style POD type because of
    the inheritance. But even if the memory layout is up to the
    implementation the offset of m3 should be stable.

    I use this in a wrapper to a C library which takes struct A* as type and
    requires relative offsets to custom components. I would prefer not to
    use aggregation because A has many members and this would significantly
    blow up the code.

    Is it undefined behavior or is it only a warning?


    Marcel
     
    Marcel Müller, Sep 21, 2009
    #1
    1. Advertising

  2. On 21 Set, 20:20, Marcel Müller <> wrote:
    > When I switch an existing project to gcc I get dozens of warnings
    > concerning offsetof. The code runs fine with and without gcc. Of course
    > this means nothing.
    >
    > Example:
    >
    > #include <stddef.h>
    >
    > #define structoffsetof(type, base) ((int)(static_cast<base*>((type*)64))-64)
    >
    > struct A
    > { int m1;
    >    int m2;
    >
    > };
    >
    > struct B : A
    > { int m3;
    >
    > };
    >
    > int main()
    > { A a;
    >    a.m1 = offsetof(B, m3) - structoffsetof(B, A);
    >    return 0;
    >
    > }
    >
    > test.cpp: In function `int main()':
    > test.cpp:16: warning: invalid access to non-static data member `A::m1'
    > of NULL object
    > test.cpp:16: warning: (perhaps the `offsetof' macro was used incorrectly)
    >
    > Of course, it is because B is no longer a C style POD type because of
    > the inheritance. But even if the memory layout is up to the
    > implementation the offset of m3 should be stable.
    >
    > I use this in a wrapper to a C library which takes struct A* as type and
    > requires relative offsets to custom components. I would prefer not to
    > use aggregation because A has many members and this would significantly
    > blow up the code.
    >
    > Is it undefined behavior or is it only a warning?


    I have no answer to your questions but I have a question on my turn:
    isn't there any better way to achieve the same result other than using
    those macros?

    I mean, if you want the offset of B over its base A, isn't enough to
    call sizeof(A)?

    Also, to get the offset of a member over the base address of an
    object, isn't enough to compute the difference between those two
    addresses?

    I feel a bit confused.

    Francesco
    --
    Francesco S. Carta, hobbyist
    http://fscode.altervista.org
     
    Francesco S. Carta, Sep 21, 2009
    #2
    1. Advertising

  3. On 21 Set, 20:51, "Francesco S. Carta" <> wrote:
    > On 21 Set, 20:20, Marcel Müller <> wrote:
    >
    >
    >
    > > When I switch an existing project to gcc I get dozens of warnings
    > > concerning offsetof. The code runs fine with and without gcc. Of course
    > > this means nothing.

    >
    > > Example:

    >
    > > #include <stddef.h>

    >
    > > #define structoffsetof(type, base) ((int)(static_cast<base*>((type*)64))-64)

    >
    > > struct A
    > > { int m1;
    > >    int m2;

    >
    > > };

    >
    > > struct B : A
    > > { int m3;

    >
    > > };

    >
    > > int main()
    > > { A a;
    > >    a.m1 = offsetof(B, m3) - structoffsetof(B, A);
    > >    return 0;

    >
    > > }

    >
    > > test.cpp: In function `int main()':
    > > test.cpp:16: warning: invalid access to non-static data member `A::m1'
    > > of NULL object
    > > test.cpp:16: warning: (perhaps the `offsetof' macro was used incorrectly)

    >
    > > Of course, it is because B is no longer a C style POD type because of
    > > the inheritance. But even if the memory layout is up to the
    > > implementation the offset of m3 should be stable.

    >
    > > I use this in a wrapper to a C library which takes struct A* as type and
    > > requires relative offsets to custom components. I would prefer not to
    > > use aggregation because A has many members and this would significantly
    > > blow up the code.

    >
    > > Is it undefined behavior or is it only a warning?

    >
    > I have no answer to your questions but I have a question on my turn:
    > isn't there any better way to achieve the same result other than using
    > those macros?
    >
    > I mean, if you want the offset of B over its base A, isn't enough to
    > call sizeof(A)?
    >
    > Also, to get the offset of a member over the base address of an
    > object, isn't enough to compute the difference between those two
    > addresses?
    >
    > I feel a bit confused.


    Also, I think that either there is a problem in your structoffsetof
    macro or in my understanding of your purposes (likely so) because it
    equates to zero while I would have expected it to equate to "sizeof
    (A)".

    About the other macro, "offsetof(B, m3) == (size_t(&b.m3) - size_t
    (&b))" on my system, hence I cannot see the need for that macro, which
    does castings all over the place.

    Where exactly did I lose myself?

    Francesco
    --
    Francesco S. Carta, hobbyist
    http://fscode.altervista.org
     
    Francesco S. Carta, Sep 21, 2009
    #3
  4. Francesco S. Carta wrote:
    > Also, I think that either there is a problem in your structoffsetof
    > macro or in my understanding of your purposes (likely so) because it
    > equates to zero while I would have expected it to equate to "sizeof
    > (A)".


    Zero is correct for the most implementations, since A is the first base
    class of B and both are non-polymorphic. But this macro does not raise
    the warning. Calling offsetof is the problem.

    > About the other macro, "offsetof(B, m3) == (size_t(&b.m3) - size_t
    > (&b))" on my system,


    The offset is needed relative to the start of A because A* is passed to
    the C library.

    > hence I cannot see the need for that macro, which
    > does castings all over the place.


    If you already have an instance of your type, you are right, but
    otherwise you have to make your own pseudo-instance. And that is exactly
    the way the macros offsetof and my structoffsetof work with all that casts.

    Furthermore when dealing with an instance I would prefer to cast to
    char*: (char*)&b.m3 - (char*)&(A&)b


    Marcel
     
    Marcel Müller, Sep 21, 2009
    #4
  5. On 22 Set, 00:16, Marcel Müller <> wrote:
    > Francesco S. Carta wrote:
    > > Also, I think that either there is a problem in your structoffsetof
    > > macro or in my understanding of your purposes (likely so) because it
    > > equates to zero while I would have expected it to equate to "sizeof
    > > (A)".

    >
    > Zero is correct for the most implementations, since A is the first base
    > class of B and both are non-polymorphic. But this macro does not raise
    > the warning. Calling offsetof is the problem.
    >
    > > About the other macro, "offsetof(B, m3) == (size_t(&b.m3) - size_t
    > > (&b))" on my system,

    >
    > The offset is needed relative to the start of A because A* is passed to
    > the C library.
    >
    > > hence I cannot see the need for that macro, which
    > > does castings all over the place.

    >
    > If you already have an instance of your type, you are right, but
    > otherwise you have to make your own pseudo-instance. And that is exactly
    > the way the macros offsetof and my structoffsetof work with all that casts.
    >
    > Furthermore when dealing with an instance I would prefer to cast to
    > char*: (char*)&b.m3 - (char*)&(A&)b


    Thanks for your explanation, now it's clearer.

    I still think you could avoid those macros, also you could avoid C-
    style casts.

    I'm just saying this because I've been told several times to never use
    C-style casts, and also to avoid macros whenever possible - and for
    such cases, I've been told to make macros all-caps.

    Have you considered making some template functions to replace those
    macros?

    As I said, I have no answer for your specific issue, I'm just
    bordering along, sorry.

    Have good coding,
    Francesco
    --
    Francesco S. Carta, hobbyist
    http://fscode.altervista.org
     
    Francesco S. Carta, Sep 21, 2009
    #5
  6. Marcel Müller

    Krice Guest

    On 22 syys, 01:16, Marcel Müller <> wrote:
    > Calling offsetof is the problem.


    It's the kind of problem that should be the end of career
    for anyone who accepted that as valid source code.
     
    Krice, Sep 22, 2009
    #6
  7. On 22 Set, 08:23, Krice <> wrote:
    > On 22 syys, 01:16, Marcel Müller <> wrote:
    >
    > > Calling offsetof is the problem.

    >
    > It's the kind of problem that should be the end of career
    > for anyone who accepted that as valid source code.


    Wow, is using offsetof _that_ bad?

    I don't like that macro, fine, but can one be rightfully fired for
    using it or for letting it pass during a code review?

    Amazing. I must be either overvaluing peoples' jobs or undervaluing
    coding rules, otherwise you should be voluntarily exaggerating - with
    all the best purposes, of course.

    Have good time,
    Francesco
    --
    Francesco S. Carta, hobbyist
    http://fscode.altervista.org
     
    Francesco S. Carta, Sep 22, 2009
    #7
  8. Francesco S. Carta wrote:
    >> If you already have an instance of your type, you are right, but
    >> otherwise you have to make your own pseudo-instance. And that is exactly
    >> the way the macros offsetof and my structoffsetof work with all that casts.
    >>
    >> Furthermore when dealing with an instance I would prefer to cast to
    >> char*: (char*)&b.m3 - (char*)&(A&)b

    >
    > Thanks for your explanation, now it's clearer.


    I just saw a bug in the example, which may have caused confusion.
    It should have been:
    void foo(A*);
    int main()
    { B b;
    b.m1 = offsetof(B, m3) - structoffsetof(B, A);
    foo(&b);
    return 0;
    }

    > I still think you could avoid those macros, also you could avoid C-
    > style casts.


    I think there is no way of implementing things like offsetof without a
    C-style cast.

    > I'm just saying this because I've been told several times to never use
    > C-style casts,


    This works unless you have to deal with void* or something like offsetof.

    > and also to avoid macros whenever possible - and for
    > such cases, I've been told to make macros all-caps.
    >
    > Have you considered making some template functions to replace those
    > macros?


    Yes, just after my last post. Unfortunately while it is trivial to write
    a template for structoffsetof I see no way to implement offsetof via a
    template. It is because you cannot reasonably pass the element selector
    as template argument without using lambda expressions. The latter would
    require a very modern compiler because of the return type deduction.
    Furthermore the compiler might no longer check that it is a compile time
    constant.


    > As I said, I have no answer for your specific issue, I'm just
    > bordering along, sorry.


    I think I will find a way to avoid this annoying warnings, again using
    macros.
    But I still wonder why offsetof should be invalid for inherited types.
    The result of offsetof is not portable anyway because of alignment and
    type size issues. But within a given plattform the result should be
    stable as long as you do not deal with virtual base classes.


    Marcel
     
    Marcel Müller, Sep 22, 2009
    #8
  9. On 22 sep, 00:39, "Francesco S. Carta" <> wrote:
    > On 22 Set, 00:16, Marcel Müller <> wrote:
    >
    >
    >
    > > Francesco S. Carta wrote:
    > > > Also, I think that either there is a problem in your structoffsetof
    > > > macro or in my understanding of your purposes (likely so) because it
    > > > equates to zero while I would have expected it to equate to "sizeof
    > > > (A)".

    >
    > > Zero is correct for the most implementations, since A is the first base
    > > class of B and both are non-polymorphic. But this macro does not raise
    > > the warning. Calling offsetof is the problem.

    >
    > > > About the other macro, "offsetof(B, m3) == (size_t(&b.m3) - size_t
    > > > (&b))" on my system,

    >
    > > The offset is needed relative to the start of A because A* is passed to
    > > the C library.

    >
    > > > hence I cannot see the need for that macro, which
    > > > does castings all over the place.

    >
    > > If you already have an instance of your type, you are right, but
    > > otherwise you have to make your own pseudo-instance. And that is exactly
    > > the way the macros offsetof and my structoffsetof work with all that casts.

    >
    > > Furthermore when dealing with an instance I would prefer to cast to
    > > char*: (char*)&b.m3 - (char*)&(A&)b

    >
    > Thanks for your explanation, now it's clearer.
    >
    > I still think you could avoid those macros, also you could avoid C-
    > style casts.
    >
    > I'm just saying this because I've been told several times to never use
    > C-style casts, and also to avoid macros whenever possible - and for
    > such cases, I've been told to make macros all-caps.
    >
    > Have you considered making some template functions to replace those
    > macros?
    >
    > As I said, I have no answer for your specific issue, I'm just
    > bordering along, sorry.


    offsetof() only works with POD type. Since you have an inheritance, it
    is no longer a POD type (the same would happen if you had defined
    private data or defined a constructor).

    The next standard defines a memory layout (standard layout) that will
    makes this code conformant but it is not yet the case.

    As it is, you could rewrite your code using composition or an union by
    copying A members into B.

    You will be able to cast a A* into a B* but you loose the type
    checking (as in C).

    --
    Michael
     
    Michael Doubez, Sep 22, 2009
    #9
  10. Michael Doubez wrote:
    > offsetof() only works with POD type. Since you have an inheritance, it
    > is no longer a POD type (the same would happen if you had defined
    > private data or defined a constructor).
    >
    > The next standard defines a memory layout (standard layout) that will
    > makes this code conformant but it is not yet the case.


    Thanks! That's what I want to know.

    > As it is, you could rewrite your code using composition or an union by
    > copying A members into B.


    Of course. But the code now works and it will work in future. So I will
    concentrate on to get rid of the warnings.

    > You will be able to cast a A* into a B* but you loose the type
    > checking (as in C).


    Using composition there is a cleaner way.


    Marcel
     
    Marcel Müller, Sep 22, 2009
    #10
  11. On 22 Set, 15:10, Marcel Müller <> wrote:
    > Francesco S. Carta wrote:
    > > I still think you could avoid those macros, also you could avoid C-
    > > style casts.

    >
    > I think there is no way of implementing things like offsetof without a
    > C-style cast.


    We are already beyond the edges of my firm ground, hence I'll stop
    here with just this note: the GCC implementation of offsetof makes no
    use of C-style casts - they are replaced by reinterpret_cast. As I
    said before, I've been told to never use C-style casts, because the
    explicitly-named casts are more visible and can achieve just the same
    results.

    FWIW, just another two cents.

    Have good coding,
    Francesco
    --
    Francesco S. Carta, hobbyist
    http://fscode.altervista.org
     
    Francesco S. Carta, Sep 22, 2009
    #11
  12. Marcel Müller

    James Kanze Guest

    On Sep 21, 8:20 pm, Marcel Müller <>
    wrote:
    > When I switch an existing project to gcc I get dozens of
    > warnings concerning offsetof. The code runs fine with and
    > without gcc. Of course this means nothing.


    > Example:


    > #include <stddef.h>


    > #define structoffsetof(type, base) ((int)(static_cast<base*>((type*)64))-64)


    Note that there's a reinterpret_cast in there (the C style cast)
    with very much implementation defined behavior (and an
    implementation may define it to be undefined behavior).

    > struct A
    > {
    > int m1;
    > int m2;
    > };


    > struct B : A
    > {
    > int m3;
    > };


    > int main()
    > {
    > A a;
    > a.m1 = offsetof(B, m3) - structoffsetof(B, A);


    And the offsetof here is undefined behavior. On recent versions
    of g++, I think it fails to compile (but I don't have access to
    any machine with a recent version of g++ installed to check).

    > return 0;
    > }


    > test.cpp: In function `int main()':
    > test.cpp:16: warning: invalid access to non-static data member `A::m1'
    > of NULL object
    > test.cpp:16: warning: (perhaps the `offsetof' macro was used incorrectly)


    > Of course, it is because B is no longer a C style POD type
    > because of the inheritance. But even if the memory layout is
    > up to the implementation the offset of m3 should be stable.


    Maybe. Who knows? Using offsetof on anything but a POD is
    undefined behavior, and I would expect a good implementation to
    reject it at compile time.

    > I use this in a wrapper to a C library which takes struct A*
    > as type and requires relative offsets to custom components. I
    > would prefer not to use aggregation because A has many members
    > and this would significantly blow up the code.


    > Is it undefined behavior or is it only a warning?


    Totally undefined behavior.

    --
    James Kanze
     
    James Kanze, Sep 22, 2009
    #12
  13. Marcel Müller

    James Kanze Guest

    On Sep 22, 3:10 pm, Marcel Müller <>
    wrote:
    > Francesco S. Carta wrote:
    > >> If you already have an instance of your type, you are
    > >> right, but otherwise you have to make your own
    > >> pseudo-instance. And that is exactly the way the macros
    > >> offsetof and my structoffsetof work with all that casts.


    > >> Furthermore when dealing with an instance I would prefer to
    > >> cast to char*: (char*)&b.m3 - (char*)&(A&)b


    > > Thanks for your explanation, now it's clearer.


    > I just saw a bug in the example, which may have caused confusion.
    > It should have been:
    > void foo(A*);
    > int main()
    > { B b;
    > b.m1 = offsetof(B, m3) - structoffsetof(B, A);
    > foo(&b);
    > return 0;
    > }


    > > I still think you could avoid those macros, also you could
    > > avoid C- style casts.


    > I think there is no way of implementing things like offsetof
    > without a C-style cast.


    The most obvious way of implementing offsetof is something like:
    #define offsetof( type, elem ) __builtin_offsetof( type, elem )
    No casts, C-style or otherwise.

    > > I'm just saying this because I've been told several times to
    > > never use C-style casts,


    > This works unless you have to deal with void* or something
    > like offsetof.


    No. The only thing a C-style cast can do that static_cast or
    reinterpret_cast (combined with const_cast) can't is cast to a
    private base class.

    --
    James Kanze
     
    James Kanze, Sep 22, 2009
    #13
  14. On 22 Set, 23:03, James Kanze <> wrote:
    > On Sep 22, 3:10 pm, Marcel Müller <>
    > wrote:
    >
    >
    >
    > > Francesco S. Carta wrote:
    > > >> If you already have an instance of your type, you are
    > > >> right, but otherwise you have to make your own
    > > >> pseudo-instance. And that is exactly the way the macros
    > > >> offsetof and my structoffsetof work with all that casts.
    > > >> Furthermore when dealing with an instance I would prefer to
    > > >> cast to char*: (char*)&b.m3 - (char*)&(A&)b
    > > > Thanks for your explanation, now it's clearer.

    > > I just saw a bug in the example, which may have caused confusion.
    > > It should have been:
    > >    void foo(A*);
    > >    int main()
    > >    { B b;
    > >      b.m1 = offsetof(B, m3) - structoffsetof(B, A);
    > >      foo(&b);
    > >      return 0;
    > >    }
    > > > I still think you could avoid those macros, also you could
    > > > avoid C- style casts.

    > > I think there is no way of implementing things like offsetof
    > > without a C-style cast.

    >
    > The most obvious way of implementing offsetof is something like:
    >     #define offsetof( type, elem ) __builtin_offsetof( type, elem )
    > No casts, C-style or otherwise.
    >
    > > > I'm just saying this because I've been told several times to
    > > > never use C-style casts,

    > > This works unless you have to deal with void* or something
    > > like offsetof.

    >
    > No.  The only thing a C-style cast can do that static_cast or
    > reinterpret_cast (combined with const_cast) can't is cast to a
    > private base class.


    Uh... black magic... but, wait: GCC 3.4.5 allows me reinterpret_cast-
    ing a derived class to all of its base classes, even to private
    ones... is that a bug?

    At least on my system, seems that I really don't need C-style casts at
    all, I think I won't upgrade it very soon ;-)

    Have good time,
    Francesco
    --
    Francesco S. Carta, hobbyist
    http://fscode.altervista.org
     
    Francesco S. Carta, Sep 22, 2009
    #14
  15. On 22 sep, 16:40, Marcel Müller <> wrote:
    > Michael Doubez wrote:
    > > offsetof() only works with POD type. Since you have an inheritance, it
    > > is no longer a POD type (the same would happen if you had defined
    > > private data or defined a constructor).

    >
    > > The next standard defines a memory layout (standard layout) that will
    > > makes this code conformant but it is not yet the case.

    >
    > Thanks! That's what I want to know.
    >
    > > As it is, you could rewrite your code using composition or an union by
    > > copying A members into B.

    >
    > Of course. But the code now works and it will work in future. So I will
    > concentrate on to get rid of the warnings.


    I find that a bit optimistic.

    It is working on your compiler, at its given version, with the
    compilations flags you are using and in the cases you are currently
    using ...

    And the standard will not be out before a year or more which is to say
    that compiler will then have to implement that feature reliably.

    > > You will be able to cast a A* into a B* but you loose the type
    > > checking (as in C).

    >
    > Using composition there is a cleaner way.


    Personally, I would indeed use composition perhaps with a cast
    operator to (const) A&.

    --
    Michael
     
    Michael Doubez, Sep 23, 2009
    #15
  16. Marcel Müller

    James Kanze Guest

    On Sep 22, 11:05 pm, "Francesco S. Carta" <> wrote:
    > On 22 Set, 23:03, James Kanze <> wrote:


    [...]
    > > No. The only thing a C-style cast can do that static_cast
    > > or reinterpret_cast (combined with const_cast) can't is cast
    > > to a private base class.


    > Uh... black magic... but, wait: GCC 3.4.5 allows me
    > reinterpret_cast- ing a derived class to all of its base
    > classes, even to private ones... is that a bug?


    No. But it's not doing what you think---reinterpret_cast
    doesn't cast up and down in a hierarchy; it tells the compiler
    that the actual pointer you have points to some different type.

    To get an idea of what I mean, try the following:

    #include <iostream>

    struct A { int a ; } ;
    struct B { int b ; } ;
    struct C : A, B { int c ; } ;

    int
    main()
    {
    C obj ;
    obj.a = 1 ;
    obj.b = 2 ;
    obj.c = 3 ;
    C* pc = &obj ;
    B* pb1 = static_cast< B* >( pc ) ;
    B* pb2 = reinterpret_cast< B* >( pc ) ;
    std::cout << "using static_cast: " << pb1 << " (" << pb1->b <<
    ")\n" ;
    std::cout << "using reinterpret_cast: " << pb2 << " (" << pb2-
    >b << ")\n" ;

    }

    > At least on my system, seems that I really don't need C-style
    > casts at all, I think I won't upgrade it very soon ;-)


    You generally don't need C-style casts. Or reinterpret_cast
    either, for that matter.

    --
    James Kanze
     
    James Kanze, Sep 23, 2009
    #16
  17. On 23 Set, 10:06, James Kanze <> wrote:
    > On Sep 22, 11:05 pm, "Francesco S. Carta" <> wrote:
    >
    > > On 22 Set, 23:03, James Kanze <> wrote:

    >
    > [...]
    >
    > > > No. The only thing a C-style cast can do that static_cast
    > > > or reinterpret_cast (combined with const_cast) can't is cast
    > > > to a private base class.

    > > Uh... black magic... but, wait: GCC 3.4.5 allows me
    > > reinterpret_cast- ing a derived class to all of its base
    > > classes, even to private ones... is that a bug?

    >
    > No. But it's not doing what you think---reinterpret_cast
    > doesn't cast up and down in a hierarchy; it tells the compiler
    > that the actual pointer you have points to some different type.


    You're right and you got me on that, it didn't do _exactly_ what I
    thought, but still, it is going there. Read on please.

    > To get an idea of what I mean, try the following:
    >
    > #include <iostream>
    >
    > struct A { int a ; } ;
    > struct B { int b ; } ;
    > struct C : A, B { int c ; } ;
    >
    > int
    > main()
    > {
    > C obj ;
    > obj.a = 1 ;
    > obj.b = 2 ;
    > obj.c = 3 ;
    > C* pc = &obj ;
    > B* pb1 = static_cast< B* >( pc ) ;
    > B* pb2 = reinterpret_cast< B* >( pc ) ;
    > std::cout << "using static_cast: " << pb1 << " (" << pb1->b <<
    > ")\n" ;
    > std::cout << "using reinterpret_cast: " << pb2 << " (" << pb2->b << ")\n" ;
    >
    > }


    Thanks for pointing this out. The first time I've tried it only by
    calling functions of the base classes after having used
    reinterpret_cast, hence I didn't realize that data displacement
    problem.

    But now that you have made me dig the problem further, I feel to say
    that your original statement:

    "The only thing a C-style cast can do that static_cast or
    reinterpret_cast (combined with const_cast) can't is cast to a private
    base class."

    ....does not apply _exactly_ as it is.

    I know that I am nitpicking right now, I just want to show that we
    never really need C-style casts, even when doing weird hacks.

    Of course, since my knowledge is what it is, you might come up with
    something else that cannot really be done without C-style casts, and
    I'll eagerly learn something new, then.

    Consider the following:

    -------
    #include <iostream>
    #include <string>

    using namespace std;

    struct A {
    string data;
    A() : data("A's data") {}
    string foo() {
    return "A, private base";
    }
    };

    struct B {
    string data;
    B() : data("B's data") {}
    string foo() {
    return "B, protected base";
    }
    };

    struct C : private A, protected B {
    string data;
    C() : data("C's data") {}
    string foo() {
    return "C, derived class";
    }
    };

    int main() {

    C c;

    cout << " &c == " << size_t(&c) << endl;
    cout << "c.foo() == " << c.foo() << endl;
    cout << " c.data == " << c.data << endl << endl;

    B& b = *reinterpret_cast<B*>(size_t(&c) + sizeof(A));

    cout << " &b == " << size_t(&b) << endl;
    cout << "b.foo() == " << b.foo() << endl;
    cout << " b.data == " << b.data << endl << endl;

    A& a = reinterpret_cast<A&>(c);

    cout << " &a == " << size_t(&a) << endl;
    cout << "a.foo() == " << a.foo() << endl;
    cout << " a.data == " << a.data << endl;

    return 0;
    }
    -------

    And its output:

    -------
    &c == 2359120
    c.foo() == C, derived class
    c.data == C's data

    &b == 2359124
    b.foo() == B, protected base
    b.data == B's data

    &a == 2359120
    a.foo() == A, private base
    a.data == A's data
    -------

    I've got to a private base with just a reinterpret_cast. Fine, it
    worked because A is the first base of C.

    I've also got to a protected, non-first base by feeding the
    appropriate address + offset to reinterpret_cast - which is what the C-
    style cast does alone, and which is about the displacement problem you
    have shown in your code.

    In other words, your original statement could be changed to something
    along the lines of:

    "There is nothing that a C-style cast can do which the C++ explicit
    casts cannot, they only make it less comfortable to get to a non-
    public, non-first base class of a derived class."

    > > At least on my system, seems that I really don't need C-style
    > > casts at all, I think I won't upgrade it very soon ;-)

    >
    > You generally don't need C-style casts. Or reinterpret_cast
    > either, for that matter.


    Of course.

    Anyway, here we have stepped again into the "tricks and hacks" realm -
    and eventually also into the "UB" realm, I'm not sure about.

    Thanks for your patience with me ;-)
    cheers,
    Francesco
    --
    Francesco S. Carta, hobbyist
    http://fscode.altervista.org
     
    Francesco S. Carta, Sep 23, 2009
    #17
  18. Hallo,

    Michael Doubez wrote:
    >>> As it is, you could rewrite your code using composition or an union by
    >>> copying A members into B.

    >> Of course. But the code now works and it will work in future. So I will
    >> concentrate on to get rid of the warnings.

    >
    > I find that a bit optimistic.
    >
    > It is working on your compiler, at its given version, with the
    > compilations flags you are using and in the cases you are currently
    > using ...


    well, currently three compilers in different versions are supported. IBM
    VAC++ (>=3.08), gcc (>=3.2 and untested 4.x) and OpenWatcom. Only gcc
    creates this warning. Neither IBM nor OpenWatcom nor MSVC9 (only for
    testing) does.
    And since the wrapped C library is part of a platform toolkit, the
    platform is restricted anyway to x86.

    >>> You will be able to cast a A* into a B* but you loose the type
    >>> checking (as in C).

    >> Using composition there is a cleaner way.

    >
    > Personally, I would indeed use composition perhaps with a cast
    > operator to (const) A&.


    This time I will take the easy way.


    Marcel
     
    Marcel Müller, Sep 23, 2009
    #18
  19. Marcel Müller

    James Kanze Guest

    On Sep 23, 11:28 am, "Francesco S. Carta" <> wrote:
    > On 23 Set, 10:06, James Kanze <> wrote:
    > > On Sep 22, 11:05 pm, "Francesco S. Carta"
    > > <> wrote:


    > > > On 22 Set, 23:03, James Kanze <> wrote:


    > > [...]


    > > > > No. The only thing a C-style cast can do that static_cast
    > > > > or reinterpret_cast (combined with const_cast) can't is cast
    > > > > to a private base class.
    > > > Uh... black magic... but, wait: GCC 3.4.5 allows me
    > > > reinterpret_cast- ing a derived class to all of its base
    > > > classes, even to private ones... is that a bug?


    > > No. But it's not doing what you think---reinterpret_cast
    > > doesn't cast up and down in a hierarchy; it tells the compiler
    > > that the actual pointer you have points to some different type.


    > You're right and you got me on that, it didn't do _exactly_
    > what I thought, but still, it is going there. Read on please.


    A reinterpret_cast reinterprets the type of a pointer or a
    reference. It doesn't convert within the hierarchy.

    > > To get an idea of what I mean, try the following:


    > > #include <iostream>


    > > struct A { int a ; } ;
    > > struct B { int b ; } ;
    > > struct C : A, B { int c ; } ;


    > > int
    > > main()
    > > {
    > > C obj ;
    > > obj.a = 1 ;
    > > obj.b = 2 ;
    > > obj.c = 3 ;
    > > C* pc = &obj ;
    > > B* pb1 = static_cast< B* >( pc ) ;
    > > B* pb2 = reinterpret_cast< B* >( pc ) ;
    > > std::cout << "using static_cast: " << pb1 << " (" << pb1->b <<
    > > ")\n" ;
    > > std::cout << "using reinterpret_cast: " << pb2 << " (" << pb2->b << ")\n" ;
    > > }


    > Thanks for pointing this out. The first time I've tried it
    > only by calling functions of the base classes after having
    > used reinterpret_cast, hence I didn't realize that data
    > displacement problem.


    Note that my code contains undefined behavior. I happen to know
    what it does on the usual implementations (VC++, g++, etc.), but
    there's absolutely no guarantee. I just posted it to show a
    little what's going on under the hood.

    > But now that you have made me dig the problem further, I feel
    > to say that your original statement:


    > "The only thing a C-style cast can do that static_cast or
    > reinterpret_cast (combined with const_cast) can't is cast to a
    > private base class."


    > ...does not apply _exactly_ as it is.


    > I know that I am nitpicking right now, I just want to show that we
    > never really need C-style casts, even when doing weird hacks.


    > Of course, since my knowledge is what it is, you might come up
    > with something else that cannot really be done without C-style
    > casts, and I'll eagerly learn something new, then.


    > Consider the following:


    > -------
    > #include <iostream>
    > #include <string>


    > using namespace std;


    > struct A {
    > string data;
    > A() : data("A's data") {}
    > string foo() {
    > return "A, private base";
    > }
    > };


    > struct B {
    > string data;
    > B() : data("B's data") {}
    > string foo() {
    > return "B, protected base";
    > }
    > };


    > struct C : private A, protected B {
    > string data;
    > C() : data("C's data") {}
    > string foo() {
    > return "C, derived class";
    > }
    > };


    > int main() {


    > C c;
    > cout << " &c == " << size_t(&c) << endl;
    > cout << "c.foo() == " << c.foo() << endl;
    > cout << " c.data == " << c.data << endl << endl;


    And all of the remaining code is undefined behavior.

    > B& b = *reinterpret_cast<B*>(size_t(&c) + sizeof(A));


    > cout << " &b == " << size_t(&b) << endl;
    > cout << "b.foo() == " << b.foo() << endl;
    > cout << " b.data == " << b.data << endl << endl;


    The above might work, or it might not. If A doesn't contain any
    data members or virtual functions, it probably won't work on a
    lot of implementations.

    > A& a = reinterpret_cast<A&>(c);


    There's also no guarantee that the A subobject is at the same
    physical address as the C object.

    > cout << " &a == " << size_t(&a) << endl;
    > cout << "a.foo() == " << a.foo() << endl;
    > cout << " a.data == " << a.data << endl;


    > return 0;
    > }
    > -------


    > And its output:


    > -------
    > &c == 2359120
    > c.foo() == C, derived class
    > c.data == C's data


    > &b == 2359124
    > b.foo() == B, protected base
    > b.data == B's data


    > &a == 2359120
    > a.foo() == A, private base
    > a.data == A's data
    > -------


    > I've got to a private base with just a reinterpret_cast. Fine,
    > it worked because A is the first base of C.


    It worked because you happened to guess the way your compiler
    lays out classes. There's absolutely no guarantee, and the
    behavior is completely undefined.

    Note that the standard makes absolutely no guarantees with
    regards to class layout; the compiler can do pretty much
    anything it wishes.

    Also, even if it worked, it's a lot less maintainable than the C
    style cast. The reason you should avoid C style casts (when
    pointers or references are involved---otherwise, it depends) is
    because there is normally something better to use. If there's
    not something better, of course, you shouldn't force the issue
    by using something worse. (On the other hand, you might ask
    yourself questions as to why you want to do this. If the
    inheritance is private, it's probably private for a reason.)

    --
    James Kanze
     
    James Kanze, Sep 24, 2009
    #19
  20. On 24 Set, 09:40, James Kanze <> wrote:
    > On Sep 23, 11:28 am, "Francesco S. Carta" <> wrote:
    >
    >
    >
    > > On 23 Set, 10:06, James Kanze <> wrote:
    > > > On Sep 22, 11:05 pm, "Francesco S. Carta"
    > > > <> wrote:
    > > > > On 22 Set, 23:03, James Kanze <> wrote:
    > > >     [...]
    > > > > > No.  The only thing a C-style cast can do that static_cast
    > > > > > or reinterpret_cast (combined with const_cast) can't is cast
    > > > > > to a private base class.
    > > > > Uh... black magic... but, wait: GCC 3.4.5 allows me
    > > > > reinterpret_cast- ing a derived class to all of its base
    > > > > classes, even to private ones... is that a bug?
    > > > No.  But it's not doing what you think---reinterpret_cast
    > > > doesn't cast up and down in a hierarchy; it tells the compiler
    > > > that the actual pointer you have points to some different type.

    > > You're right and you got me on that, it didn't do _exactly_
    > > what I thought, but still, it is going there. Read on please.

    >
    > A reinterpret_cast reinterprets the type of a pointer or a
    > reference.  It doesn't convert within the hierarchy.
    >
    >
    >
    > > > To get an idea of what I mean, try the following:
    > > >     #include <iostream>
    > > >     struct A { int a ; } ;
    > > >     struct B { int b ; } ;
    > > >     struct C : A, B { int c ; } ;
    > > >     int
    > > >     main()
    > > >     {
    > > >         C           obj ;
    > > >         obj.a = 1 ;
    > > >         obj.b = 2 ;
    > > >         obj.c = 3 ;
    > > >         C*          pc = &obj ;
    > > >         B*          pb1 = static_cast< B* >( pc ) ;
    > > >         B*          pb2 = reinterpret_cast< B* >( pc ) ;
    > > >         std::cout << "using static_cast: " << pb1 << " (" << pb1->b <<
    > > > ")\n" ;
    > > >         std::cout << "using reinterpret_cast: " << pb2 << " (" << pb2->b << ")\n" ;
    > > >     }

    > > Thanks for pointing this out. The first time I've tried it
    > > only by calling functions of the base classes after having
    > > used reinterpret_cast, hence I didn't realize that data
    > > displacement problem.

    >
    > Note that my code contains undefined behavior.  I happen to know
    > what it does on the usual implementations (VC++, g++, etc.), but
    > there's absolutely no guarantee.  I just posted it to show a
    > little what's going on under the hood.
    >
    >
    >
    > > But now that you have made me dig the problem further, I feel
    > > to say that your original statement:
    > > "The only thing a C-style cast can do that static_cast or
    > > reinterpret_cast (combined with const_cast) can't is cast to a
    > > private base class."
    > > ...does not apply _exactly_ as it is.
    > > I know that I am nitpicking right now, I just want to show that we
    > > never really need C-style casts, even when doing weird hacks.
    > > Of course, since my knowledge is what it is, you might come up
    > > with something else that cannot really be done without C-style
    > > casts, and I'll eagerly learn something new, then.
    > > Consider the following:
    > > -------
    > > #include <iostream>
    > > #include <string>
    > > using namespace std;
    > > struct A {
    > >   string data;
    > >   A() : data("A's data") {}
    > >   string foo() {
    > >     return "A, private base";
    > >   }
    > > };
    > > struct B {
    > >   string data;
    > >   B() : data("B's data") {}
    > >   string foo() {
    > >     return "B, protected base";
    > >   }
    > > };
    > > struct C : private A, protected B {
    > >   string data;
    > >   C() : data("C's data") {}
    > >   string foo() {
    > >     return "C, derived class";
    > >   }
    > > };
    > > int main() {
    > >   C c;
    > >   cout << "     &c == " << size_t(&c) << endl;
    > >   cout << "c.foo() == " << c.foo() << endl;
    > >   cout << " c.data == " << c.data << endl << endl;

    >
    > And all of the remaining code is undefined behavior.
    >
    > >   B& b = *reinterpret_cast<B*>(size_t(&c) + sizeof(A));
    > >   cout << "     &b == " << size_t(&b) << endl;
    > >   cout << "b.foo() == " << b.foo() << endl;
    > >   cout << " b.data == " << b.data << endl << endl;

    >
    > The above might work, or it might not.  If A doesn't contain any
    > data members or virtual functions, it probably won't work on a
    > lot of implementations.
    >
    > >   A& a = reinterpret_cast<A&>(c);

    >
    > There's also no guarantee that the A subobject is at the same
    > physical address as the C object.
    >
    >
    >
    > >   cout << "     &a == " << size_t(&a) << endl;
    > >   cout << "a.foo() == " << a.foo() << endl;
    > >   cout << " a.data == " << a.data << endl;
    > >   return 0;
    > > }
    > > -------
    > > And its output:
    > > -------
    > >      &c == 2359120
    > > c.foo() == C, derived class
    > >  c.data == C's data
    > >      &b == 2359124
    > > b.foo() == B, protected base
    > >  b.data == B's data
    > >      &a == 2359120
    > > a.foo() == A, private base
    > >  a.data == A's data
    > > -------
    > > I've got to a private base with just a reinterpret_cast. Fine,
    > > it worked because A is the first base of C.

    >
    > It worked because you happened to guess the way your compiler
    > lays out classes.  There's absolutely no guarantee, and the
    > behavior is completely undefined.
    >
    > Note that the standard makes absolutely no guarantees with
    > regards to class layout; the compiler can do pretty much
    > anything it wishes.


    Heck, I must definitely stop making all such assumptions... I have
    read some other threads here and on clc++m about memory layout, PODs
    and mandated behavior, and now it's placid that I completely mistaken
    those things.

    So, if I finally got it (and your explanations) straight, PODs _have_
    well defined, mandated layouts, while all other user-defined classes/
    structs have no mandated layout at all.

    > Also, even if it worked, it's a lot less maintainable than the C
    > style cast.  The reason you should avoid C style casts (when
    > pointers or references are involved---otherwise, it depends) is
    > because there is normally something better to use.  If there's
    > not something better, of course, you shouldn't force the issue
    > by using something worse.  (On the other hand, you might ask
    > yourself questions as to why you want to do this.  If the
    > inheritance is private, it's probably private for a reason.)


    Well, yes, the fact that I was swimming in weird waters was quite
    clear to me even before. Of course, all of this was meant as digging
    for the sake of digging. Straightened something and learned a couple
    of new things, well worth doing.

    Thanks again for your patience and for your explanations.

    Have good time,
    Francesco
    --
    Francesco S. Carta, hobbyist
    http://fscode.altervista.org
     
    Francesco S. Carta, Sep 24, 2009
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Hiroki Horiuchi

    g++ "offsetof" problem

    Hiroki Horiuchi, Nov 25, 2003, in forum: C++
    Replies:
    5
    Views:
    6,653
    red floyd
    Nov 25, 2003
  2. Tony Johansson

    offsetof

    Tony Johansson, Dec 16, 2004, in forum: C++
    Replies:
    1
    Views:
    465
    Alf P. Steinbach
    Dec 16, 2004
  3. Tony Johansson

    More offsetof

    Tony Johansson, Dec 16, 2004, in forum: C++
    Replies:
    3
    Views:
    524
    Mike Wahler
    Dec 18, 2004
  4. Pawel
    Replies:
    8
    Views:
    915
    Default User
    Oct 19, 2006
  5. Christopher Benson-Manica

    Re: offsetof macro and non-POD structures. Solution.

    Christopher Benson-Manica, Oct 18, 2006, in forum: C++
    Replies:
    2
    Views:
    438
    Default User
    Oct 18, 2006
Loading...

Share This Page