You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 12:27:41