Reputation: 1097
I am seeing a strange damage after normal block assertion which goes away when I make the base class non-virtual.
I've narrowed it down to the moment the actual delete call is made (original source has smart pointers but I've traced this to occur as well with just plain simple new/delete).
The strange situation is that this occurs only when the base class is marked virtual; removing the virtual declaration of the base class prevents this error. I know virtual base classes affect constructor and destructor execution order but as my class has only one base class (which is an abstract class acting as an interface) I would imagine initialization order isn't actually changed in this case.
The "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;
};
The concrete implementing class:
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;
};
Finally the code that triggers the assert:
UdpSender* sender = new UdpSender("0.0.0.0", 0);
delete sender;
To be sure it's not anything I'm doing in the constructor, destructor or any method being called i've commented out everything in the class implementation:
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)
{
/*
*/
}
Have I stumbled on an obscure compiler error? VS2019 (16.10.0), toolset v142, compiling for ISO C++17 standard.
Edit: using the address sanitizer gives a bit more information:
==8416==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x12135a727100 in thread T1: object passed to delete has wrong type: size of the allocated type: 72 bytes; size of the deallocated type: 80 bytes.
Edit2: same but with non-virtual base class still triggers the AddressSanitizer so I imagine it's not directly related to the class being virtual or not although it does appear to impact the size of the object in memory:
==18300==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x12746dba3020 in thread T1: object passed to delete has wrong type: size of the allocated type: 60 bytes; size of the deallocated type: 64 bytes.
Upvotes: 2
Views: 152
Reputation: 1097
The size differences reported by the address sanitizer eventually led me to the root cause of the problem.
Somewhere in the codebase I was using a struct with a custom packing boundary (4; the default is 8) via the following code:
#pragma pack(4)
struct Foo { ... }
Any code including the header file for this struct would size any struct/class after this include differently (4byte boundary) than code not including the header file or including the header after a struct/class definition (8byte boundary) leading to structs/classes with two different sizes for the same struct/class.
Obviously the packing should be for THAT specific struct only which is achieved using (MSVC specific I imagine):
#pragma pack(push)
#pragma pack(4)
struct Foo { ... }
#pragma pack(pop)
Upvotes: 2