为什么在C++中使用虚构造函数时会得到一个错误的指向基类的指针?

huangapple go评论130阅读模式
英文:

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

问题

当声明一个虚拟析构函数时,类型转换后的指针 sa_in6 不再指向继承的 sockaddr_storage 结构的开头,因此导致无法正确访问 AF_INET6 数据。这是因为虚拟函数表(vtable)的存在,它会影响派生类对象的内存布局。

虚拟函数表是用来实现多态性的机制,它包含了指向派生类虚拟函数的指针。当你声明一个虚拟析构函数时,编译器会在对象的内存布局中添加一个指向虚拟函数表的指针。这就是为什么使用虚拟析构函数后,sssa_in6 指向不同的内存位置的原因。

在你的代码中,ss 仍然指向 sockaddr_storage 结构的开头,因为它是一个对象,没有受到虚拟函数表的影响。但是,sa_in6 是一个指针,它受到虚拟函数表的影响,因此它指向的位置不正确。

要解决这个问题,你可以使用非虚拟析构函数的结构 Ssockaddr_storage1 来确保 sa_in6 指向正确的位置。虚拟析构函数通常只在需要多态性的类中使用,而不适合用于简单的数据结构扩展。

希望这个解释对你有所帮助。如果你有其他问题,可以继续提出。

英文:

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?

答案1

得分: 9

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

sockaddr_in6* sa_in6 = (sockaddr_in6*)&ss;

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,因为它现在在类中的位置更靠后,而是指向虚拟表指针。这将导致不符合预期的结果。

英文:

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.

答案2

得分: 1

以下是您要翻译的部分:

"其他答案(由Ingo和Sam提供)适用于C++,但为了清楚地记录强制类型转换之间的差异,您可以在C++中使用命名强制类型转换:

sockaddr_in6* sa_in6 = reinterpret_cast<sockaddr_in6*>(static_cast<::sockaddr_storage*>(&ss));

这是C++核心指南建议的做法:https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es49-if-you-must-use-a-cast-use-a-named-cast"

英文:

The other answers (by Ingo and Sam) work in C++, but to clearly document the difference between the casts you can use named casts in C++:

sockaddr_in6* sa_in6 = reinterpret_cast&lt;sockaddr_in6*&gt;(static_cast&lt;::sockaddr_storage*&gt;(&amp;ss));

This is recommended by the C++ core guidelines https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es49-if-you-must-use-a-cast-use-a-named-cast

答案3

得分: 0

As Sam Varshavchik 指出在他的回答中,问题是使用C编程语言进行的通常类型转换。这在C++上具有未定义的行为。

sockaddr_in6* sa_in6 = (sockaddr_in6*)&ss;

为了总结Sam的解释,我只需要告诉编译器使用引用地址&ss作为指针:

myns::Ssockaddr_storage2 ss;
sockaddr_in6* sa_in6 = (sockaddr_in6*)(::sockaddr_storage*)&ss;
std::cout << "sin6_family = " << sa_in6->sin6_family << "\n";
英文:

As Sam Varshavchik pointed out in his answer, the problem is the usual type cast done with the C programming language. This has undefined behavior on C++.

sockaddr_in6* sa_in6 = (sockaddr_in6*)&amp;ss;

To summarize the explanation from Sam I only have to tell the compiler to use the reference address &amp;ss as pointer:

myns::Ssockaddr_storage2 ss;
sockaddr_in6* sa_in6 = (sockaddr_in6*)(::sockaddr_storage*)&amp;ss;
std::cout &lt;&lt; &quot;sin6_family = &quot; &lt;&lt; sa_in6-&gt;sin6_family &lt;&lt; &quot;\n&quot;;

huangapple
  • 本文由 发表于 2023年5月30日 04:30:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360179.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定