c++ - 当基类被标记为虚拟时,正常 block 后的损坏

标签 c++ memory visual-c++

在正常的 block 断言之后,我看到了奇怪的损坏,当我将基类设置为非虚拟时,这种损坏就会消失。

我已经将范围缩小到实际删除调用的那一刻(原始来源有智能指针,但我已经通过简单的简单 new/delete 追踪到了这种情况的发生)。

奇怪的情况是,只有当基类被标记为virtual时才会发生这种情况;删除基类的虚拟声明可以防止此错误。我知道虚拟基类会影响构造函数和析构函数的执行顺序,但由于我的类只有一个基类(这是一个充当接口(interface)的抽象类),我想在这种情况下初始化顺序实际上不会改变。

“界面”:

class INetworkSender
{
public:
    /// <summary>
    /// Finalizes an instance of the <see cref="INetworkSender"/> class.
    /// </summary>
    virtual ~INetworkSender() {}

    /// <summary>
    /// Sends the specified data.
    /// </summary>
    /// <param name="data">The pointer to the buffer holding the data to send.</param>
    /// <param name="length">The amount of data to send.</param>
    virtual void send(const char* data, size_t length) = 0;

    /// <summary>
    /// Returns the remote address.
    /// </summary>
    virtual const std::string& address() const = 0;

    /// <summary>
    /// Returns the remote port.
    /// </summary>
    virtual uint16_t port() const = 0;
};

具体实现类:

class UdpSender
    : virtual public INetworkSender
{
public:
    /// <summary>
    /// Initializes a new instance of the <see cref="UdpSender" /> class.
    /// </summary>
    /// <param name="address">The address to which to send.</param>
    /// <param name="port">The port to which to send.</param>
    UdpSender(const std::string& address, uint16_t port);

    /// <summary>
    /// Finalizes an instance of the <see cref="UdpSender" /> class.
    /// </summary>
    virtual ~UdpSender();

    /// <summary>
    /// Sends the specified data.
    /// </summary>
    /// <param name="data">The pointer to the buffer holding the data to send.</param>
    /// <param name="length">The amount of data to send.</param>
    virtual void send(const char* data, size_t length) override;

    /// <summary>
    /// Returns the remote address.
    /// </summary>
    virtual const std::string& address() const override { return this->m_address; }

    /// <summary>
    /// Returns the remote port.
    /// </summary>
    virtual uint16_t port() const override { return this->m_port; }

private:
    /// <summary>
    /// The socket handle. Note we use a void* to abstract away different OS specific implementations!
    /// </summary>
    void* m_handle;

    /// <summary>
    /// The target address.
    /// </summary>
    std::string m_address;

    /// <summary>
    /// The target port.
    /// </summary>
    uint16_t m_port;
};

最后是触发断言的代码:

    UdpSender* sender = new UdpSender("0.0.0.0", 0);
    delete sender;

为了确保这不是我在构造函数、析构函数或任何被调用的方法中所做的任何事情,我已经注释掉了类实现中的所有内容:

UdpSender::UdpSender(const std::string& address, uint16_t port)
    : m_address(address)
    , m_port(port)
{
    /*
    */
}

UdpSender::~UdpSender()
{
    /*
    */
}


void UdpSender::send(const char* data, size_t length)
{
    /*
    */
}

我是否偶然发现了一个不起眼的编译器错误? VS2019 (16.10.0),工具集 v142,针对 ISO C++17 标准编译。

编辑:使用地址清理器可以提供更多信息:

==8416==错误:AddressSanitizer:线程 T1 中 0x12135a727100 上的新删除类型不匹配: 传递给delete的对象类型错误: 分配类型的大小:72 字节; 释放类型的大小:80 字节。

Edit2:相同,但非虚拟基类仍然会触发 AddressSanitizer,所以我想它与类是否为虚拟没有直接关系,尽管它似乎会影响内存中对象的大小:

==18300==错误:AddressSanitizer:线程 T1 中 0x12746dba3020 上的新删除类型不匹配: 传递给delete的对象类型错误: 分配类型的大小:60 字节; 释放类型的大小:64 字节。

最佳答案

地址清理程序报告的大小差异最终让我找到了问题的根本原因。

在代码库中的某个位置,我通过以下代码使用了具有自定义打包边界(4;默认为 8)的结构:

#pragma pack(4)
struct Foo { ... }

包含此结构体的头文件的任何代码都会在此包含后调整任何结构体/类的大小(4 字节边界),而与不包含头文件或在导致结构体的结构体/类定义(8 字节边界)之后包含 header 的代码不同/同一结构/类具有两种不同大小的类。

显然,打包应该仅适用于使用(我想是 MSVC 特定的)实现的特定结构:

#pragma pack(push)
#pragma pack(4)
struct Foo { ... }
#pragma pack(pop)

关于c++ - 当基类被标记为虚拟时,正常 block 后的损坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67803107/

相关文章:

c++ - 优化对缓冲区的并发写入

c++ - 在 Linux 上使用 g++ 运行 clang scan-build

c++ - 使用非类型参数的部分模板特化 : GCC vs MSVS

c++ - 如何将多个字符放入一个字符串中

c++ - 使用动态数组时,内存使用在 for 循环中不断增加。(C++)

c - 为什么我的内存检查代码不能正常工作

java - 为什么 Java 在我的 Linux 服务器上使用这么多内存?

Python "Memory Error."/节省内存

c++ - Visual C++ 2010 Beta 2 上的复制省略

c++ - 如果英特尔编译器是默认编译器,如何为 VS 2003 设置 native Microsoft 编译器?