Search code examples
c++pointersderived-class

Why do I get a wrong pointer to a base class with a virtual constructor on C++?


I want to derive a structure from the plain old trivial C structure ::sockaddr_storage so I can extend it with methods to handle its own data. I can type cast the derived structure as usual to get access to different socket addresses AF_INET6 or AF_INET stored in the structure. But I do not understand a problem when using a virtual destructor as shown below:

#include <netinet/in.h>
#include <iostream>

namespace myns {

struct Ssockaddr_storage1 : public ::sockaddr_storage {
    Ssockaddr_storage1() : ::sockaddr_storage() {}
    ~Ssockaddr_storage1() {}
};

// This is only different by using a virtual destructor.
struct Ssockaddr_storage2 : public ::sockaddr_storage {
    Ssockaddr_storage2() : ::sockaddr_storage() {}
    virtual ~Ssockaddr_storage2() {}
};

} // namespace myns

int main() {
    {
        myns::Ssockaddr_storage1 ss;

        std::cout << "Ssockaddr_storage1:\n";
        std::cout << "ss_family   = " << ss.ss_family << "\n";
        sockaddr_in6* sa_in6 = (sockaddr_in6*)&ss;
        std::cout << "sin6_family = " << sa_in6->sin6_family << "\n";
    }
    {
        myns::Ssockaddr_storage2 ss;

        std::cout << "\nSsockaddr_storage2 with virtual destructor:\n";
        std::cout << "ss_family   = " << ss.ss_family << "\n";
        sockaddr_in6* sa_in6 = (sockaddr_in6*)&ss;
        std::cout << "sin6_family = " << sa_in6->sin6_family << "\n";
    }
}

I compile it with:

~$ g++ -std=c++11 -Wall -Wpedantic -Wextra -Werror -Wuninitialized -Wsuggest-override -Wdeprecated example.cpp

The output is:

Ssockaddr_storage1:
ss_family   = 0
sin6_family = 0

Ssockaddr_storage2 with virtual destructor:
ss_family   = 0
sin6_family = 15752

As shown when using the reference ss to the inherited sockaddr_storage it always works. But when using the type casted pointer sa_in6 to access the AF_INET6 data it only works if not using a virtual destructor. If declaring a virtual destructor I get a random undefined address family number that differs from call to call. Obviously the pointer does not point to the right location.

Why the casted pointer sa_in6 does not point to the begin of the inherited sockaddr_storage structure when declaring a virtual destructor?


Solution

  • That's because this is undefined behavior.

    sockaddr_in6* sa_in6 = (sockaddr_in6*)&ss;
    

    C++ is not C (obviously). An analogous cast would be valid in C, but not in C++. In C++ this is undefined behavior, because C++'s type checking is more strict. ss is Ssockaddr_storage2, which is not related to sockaddr_in6, in any way.

    sockaddr_storage *sas= &ss;
    

    This conversion does not require a cast, since sockaddr_storage is just a superclass of Ssockaddr_storage2.

    And now that you have a POD C struct, you can close your eyes and pretend that it's really a sockaddr_in6:

    sockaddr_in6* sa_in6 = (sockaddr_in6*)sas;
    

    Now that we got this out of the way, the reason for your observed behavior is that once you introduce virtual inheritance most C++ implementations add an extra, hidden pointer to objects with virtual inheritance, to handle all the implementation details. This hidden pointer is typically placed at the beginning of the class's instances, and this invalid cast now ends up pointing not at sockaddr_storage, which now starts later in the class, but at the virtual table pointer. Hillarity ensues.