简体   繁体   中英

Damage after normal block when base class is marked virtual

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.

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM