问题描述
在正常块断言后,我看到一个奇怪的损坏,当我将基类设为非虚拟时,它消失了。
我已经将范围缩小到进行实际删除调用的那一刻(原始来源具有智能指针,但我已经通过简单的 new/delete 追踪到这种情况也会发生)。
奇怪的情况是,只有在基类被标记为virtual时才会出现这种情况;删除基类的虚拟声明可以防止此错误。我知道虚拟基类会影响构造函数和析构函数的执行顺序,但由于我的类只有一个基类(它是一个充当接口的抽象类),我认为在这种情况下初始化顺序实际上并没有改变。
“界面”:
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 字节边界)后包含头文件的代码导致结构/classes 具有两种不同大小的相同结构/类。
显然,包装应该仅用于使用(我想象的特定于 MSVC)实现的特定结构:
#pragma pack(push)
#pragma pack(4)
struct Foo { ... }
#pragma pack(pop)