在正常的 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/