C++中含虚析构的派生类转基类指针为何指向错误?
问题:继承
sockaddr_storage并添加虚析构后指针转换出错 我想从C结构体::sockaddr_storage派生出C++结构体来扩展方法,但添加虚析构后,强制转换为sockaddr_in6*访问数据时出现错误,指针指向了错误位置。
测试代码
#include <netinet/in.h> #include <iostream> namespace myns { struct Ssockaddr_storage1 : public ::sockaddr_storage { Ssockaddr_storage1() : ::sockaddr_storage() {} ~Ssockaddr_storage1() {} }; // 仅差异在于使用虚析构 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
原因分析与解决办法
核心原因:虚函数表的内存布局干扰
当C++类包含虚函数(包括虚析构)时,编译器会在对象内存的起始位置插入一个虚函数表指针(vptr),用来实现动态多态机制:
Ssockaddr_storage2的内存布局是:[vptr指针] + [继承的sockaddr_storage成员]- 直接将
&ss(Ssockaddr_storage2对象地址)强制转为sockaddr_in6*时,指针指向的是vptr的位置,而非真正的sockaddr_storage数据起始点,因此读取sin6_family时会拿到vptr的部分数据,出现随机错误值。 - 无虚析构的
Ssockaddr_storage1内存布局和基类完全一致,没有额外的vptr,所以强制转换后指针位置正确。
解决办法
方法1:改用组合而非继承(推荐)
将sockaddr_storage作为成员变量封装,避免内存布局冲突:
struct Ssockaddr_storage { ::sockaddr_storage storage; virtual ~Ssockaddr_storage() {} // 获取底层sockaddr_storage的指针 ::sockaddr_storage* get() { return &storage; } const ::sockaddr_storage* get() const { return &storage; } // 安全转换为sockaddr_in6* sockaddr_in6* as_in6() { return reinterpret_cast<sockaddr_in6*>(get()); } const sockaddr_in6* as_in6() const { return reinterpret_cast<const sockaddr_in6*>(get()); } };
方法2:继承时先转换为基类指针
如果坚持使用继承,转换时先将派生类指针转为基类指针(C++会自动调整地址跳过vptr),再转为目标类型:
// 替换原转换代码 sockaddr_storage* base_ptr = &ss; sockaddr_in6* sa_in6 = reinterpret_cast<sockaddr_in6*>(base_ptr);
内容的提问来源于stack exchange,提问作者Ingo
相关产品推荐
相关产品推荐

