为什么我会得到一个错误的指针,指向一个带有C++虚拟构造函数的基类?

e3bfsja2  于 2023-05-30  发布在  其他
关注(0)|答案(1)|浏览(126)

我想从普通的老式C结构::sockaddr_storage派生一个结构,这样我就可以用方法来扩展它来处理自己的数据。我可以像往常一样对派生结构进行类型转换,以访问存储在结构中的不同套接字地址AF_INET6或AF_INET。但我不明白使用虚拟析构函数时的问题,如下所示:

#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";
    }
}

我编译它:

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

输出为:

Ssockaddr_storage1:
ss_family   = 0
sin6_family = 0

Ssockaddr_storage2 with virtual destructor:
ss_family   = 0
sin6_family = 15752

如图所示,当使用ss引用继承的sockaddr_storage时,它总是有效的。但是当使用类型转换指针sa_in6访问AF_INET6数据时,它只在不使用虚拟析构函数时才有效。如果声明一个虚析构函数,我会得到一个随机的未定义地址族号,它随调用而异。显然,指针没有指向正确的位置。
为什么在声明虚析构函数时,强制转换的指针sa_in6不指向继承的sockaddr_storage结构的开始?

lh80um4z

lh80um4z1#

这是因为这是未定义的行为。

sockaddr_in6* sa_in6 = (sockaddr_in6*)&ss;

C不是C(显然)。类似的强制转换在C中有效,但在C中无效。在C中,这是未定义的行为,因为C的类型检查更严格。ssSsockaddr_storage2,与sockaddr_in6没有任何关系。

sockaddr_storage *sas= &ss;

这种转换不需要强制转换,因为sockaddr_storage只是Ssockaddr_storage2的超类。
现在你有了一个POD C结构体,你可以闭上眼睛,假装它真的是一个sockaddr_in6

sockaddr_in6* sa_in6 = (sockaddr_in6*)sas;

既然我们已经解决了这个问题,你观察到的行为的原因是,一旦你引入了虚拟继承,大多数C++实现都会添加一个额外的隐藏指针,指向具有虚拟继承的对象,以处理所有的实现细节。这个隐藏指针通常放在类示例的开头,并且这个无效的强制转换现在不再指向sockaddr_storage(现在在类的后面开始),而是指向虚表指针。喜乐随之而来。

相关问题