简体   繁体   English

在r值上使用移动分配运算符时发生异常

[英]Exception when move assignment operator is used on a r-value

I get a very strange crash (in debug mode only) when using the move assignment operator with a r-value: 当使用带有r值的move赋值运算符时,我遇到一个非常奇怪的崩溃(仅在调试模式下):

Cleaning and recompiling the code in debug mode does not help. 在调试模式下清理并重新编译代码无济于事。 The crash occurs before VulkanBuffer::operator=() is called. 崩溃发生在调用VulkanBuffer :: operator =()之前。

// ok
//VulkanBuffer myBuffer(logicalDevice, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE);
//buffer_ = std::move(myBuffer);

// ok
//VulkanBuffer myBuffer = VulkanBuffer(logicalDevice, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE);
//buffer_ = std::move(myBuffer);

// crash in debug mode, release mode works fine
//buffer_ = std::move( VulkanBuffer(logicalDevice, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE) );

// crash in debug mode, release mode works fine
buffer_ = VulkanBuffer(logicalDevice, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE);

I get this exception: 我得到这个例外:

Exception thrown at 0x0000000059F95E35 (nvoglv64.dll) in Vulkan.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF. 在Vulkan.exe中的0x0000000059F95E35(nvoglv64.dll)处引发了异常:0xC0000005:访问冲突读取位置0xFFFFFFFFFFFFFFFFFF。

This exception occurs in the VulkanBuffer non-default constructor VulkanBuffer非默认构造函数中发生此异常

Can anyone shed some light on this? 谁能对此有所启发? It seems to me that the code should be equivalent. 在我看来,代码应该是等效的。

The actual code is as below: 实际代码如下:

Declaration of VulkanBuffer VulkanBuffer的声明

#pragma once

#include "VulkanLogicalDevice.h"
#include "Vertex.h"

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <memory>
#include <vector>

class VulkanCommandPool;

class VulkanBuffer
{
public:
    VulkanBuffer();
    VulkanBuffer(const std::shared_ptr<VulkanLogicalDevice>& logicalDevice, VkDeviceSize deviceSizeInbytes, VkBufferUsageFlags bufferUsageFlags, VkSharingMode sharingMode);
    ~VulkanBuffer();
    VulkanBuffer(const VulkanBuffer &rhs) = delete;
    VulkanBuffer & operator=(const VulkanBuffer &rhs) = delete;
    VulkanBuffer(VulkanBuffer &&rhs) = delete;
    VulkanBuffer & operator=(VulkanBuffer &&rhs);

    VkBuffer & handle() { return buffer_; }
    const VkBuffer & handle() const { return buffer_; }
    const VkBufferCreateInfo & getBufferInfo() const { return createInfo_; }
    void copyDataFrom(const VulkanBuffer & rhs, const VulkanCommandPool &commandPool, VkDeviceSize dataSizeInBytes);

    friend void swap(VulkanBuffer &lhs, VulkanBuffer &rhs);

private:
    std::shared_ptr<VulkanLogicalDevice> logicalDevice_;
    VkBufferCreateInfo createInfo_;
    VkBuffer buffer_;
};

Definition of VulkanBuffer VulkanBuffer的定义

#include "VulkanBuffer.h"

#include "VulkanCommandPool.h"
#include "Vertex.h"
#include <iostream>

void swap(VulkanBuffer &lhs, VulkanBuffer &rhs)
{
    std::swap(lhs.logicalDevice_, rhs.logicalDevice_);
    std::swap(lhs.buffer_, rhs.buffer_);
    std::swap(lhs.createInfo_, rhs.createInfo_);
}

VulkanBuffer::VulkanBuffer()
    : buffer_(VK_NULL_HANDLE)
{}

/// \param logicalDevice        Vulkan device
/// \param deviceSizeInbytes    number of bytes of the vertices to be stored in this vertex buffer
/// \param bufferUsageFlags     what will the buffer be used for, eg VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
/// \param sharingMode          is the buffer used by more than one queue family, eg: VK_SHARING_MODE_EXCLUSIVE, VK_SHARING_MODE_CONCURRENT
VulkanBuffer::VulkanBuffer(const std::shared_ptr<VulkanLogicalDevice>& logicalDevice, VkDeviceSize deviceSizeInbytes, VkBufferUsageFlags bufferUsageFlags, VkSharingMode sharingMode)
    : logicalDevice_(logicalDevice), buffer_(VK_NULL_HANDLE)
{
    createInfo_.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    createInfo_.size = deviceSizeInbytes;
    createInfo_.usage = bufferUsageFlags; // indicates this data is for a vertex buffer.
    createInfo_.sharingMode = sharingMode; // ownership by one queue family or multiple

    if (vkCreateBuffer(logicalDevice->handle(), &createInfo_, nullptr, &buffer_) != VK_SUCCESS) {
        throw std::runtime_error("failed to create buffer!");
    }
}

VulkanBuffer::~VulkanBuffer()
{
    if (buffer_ != VK_NULL_HANDLE)
        vkDestroyBuffer(logicalDevice_->handle(), buffer_, nullptr);
}

VulkanBuffer & VulkanBuffer::operator=(VulkanBuffer &&rhs)
{
    swap(*this, rhs);
    return *this;
}

void VulkanBuffer::copyDataFrom(const VulkanBuffer & rhs, const VulkanCommandPool &commandPool, VkDeviceSize dataSizeInBytes)
{
    if (buffer_ == VK_NULL_HANDLE || rhs.buffer_ == VK_NULL_HANDLE)
    {
        std::cout << "Illegal VulkanBuffer::copyDataFrom(), one or more buffers not initialized.\n";
        return;
    }

    VkCommandBufferAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool = commandPool.handle();
    allocInfo.commandBufferCount = 1;

    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(logicalDevice_->handle(), &allocInfo, &commandBuffer);

    VkCommandBufferBeginInfo beginInfo = {};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

    vkBeginCommandBuffer(commandBuffer, &beginInfo);

    VkBufferCopy copyRegion = {};
    copyRegion.size = dataSizeInBytes;
    vkCmdCopyBuffer(commandBuffer, rhs.handle(), buffer_, 1, &copyRegion);

    vkEndCommandBuffer(commandBuffer);

    VkSubmitInfo submitInfo = {};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &commandBuffer;

    vkQueueSubmit(logicalDevice_->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE);
    vkQueueWaitIdle(logicalDevice_->getGraphicsQueue());

    vkFreeCommandBuffers(logicalDevice_->handle(), commandPool.handle(), 1, &commandBuffer);

}

edit: added this as part of Jherico's suggestion: When the class crashes, the following is printed from the class functions: 编辑:这是Jherico的建议的一部分:当类崩溃时,从类函数中打印以下内容:

handle: 0000000000000000 VulkanBuffer: default ctor()
handle: 0000000000000000 VulkanBuffer: non default ctor() start
handle: 000001B2A00C84E0 VulkanBuffer: non default ctor() end
handle: 0000000000000000 VulkanBuffer: non default ctor() start

when it does not crash, the following is printed 当它不崩溃时,将打印以下内容

handle: 0000000000000000 VulkanBuffer: default ctor()
handle: 0000000000000000 VulkanBuffer: non default ctor() start
handle: 000001989ADB56E0 VulkanBuffer: non default ctor() end
handle: 0000000000000000 VulkanBuffer: non default ctor() start
handle: 000001989ADB6310 VulkanBuffer: non default ctor() end
handle: 0000000000000000 VulkanBuffer: operator=()
handle lhs: 0000000000000000 handle rhs: 000001989ADB6310 VulkanBuffer: Swap() start
handle lhs: 000001989ADB6310 handle rhs: 0000000000000000 VulkanBuffer: Swap() end
handle: 000001989ADB6310 VulkanBuffer: copyDataFrom()
handle: 0000000000000000 VulkanBuffer: dtor() // instruction re-ordered here?
handle: 000001989ADB56E0 VulkanBuffer: dtor()

You seem to have forgoten to fully initialize the VkBufferCreateInfo (ie createInfo_ ). 您似乎已经放弃完全初始化VkBufferCreateInfo (即createInfo_ )。

You only seem to partially initialize here: 您似乎只在这里部分初始化:

createInfo_.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
createInfo_.size = deviceSizeInbytes;
createInfo_.usage = bufferUsageFlags; // indicates this data is for a vertex buffer.
createInfo_.sharingMode = sharingMode; // ownership by one queue family or multiple

That leaves these members uninitialized: pNext , flags , queueFamilyIndexCount and pQueueFamilyIndices . 这些成员pNext初始化: pNextflagsqueueFamilyIndexCountpQueueFamilyIndices Notably if pNext happens to be non- NULL , the driver will try to dereference it (and crash because the pointer points nowhere). 值得注意的是,如果pNext碰巧是非NULL ,驱动程序尝试取消引用它(并且由于指针无处指向而崩溃)。

This is the first kind of bugs you should be thinking of if something happens only in a Debug profile. 如果仅在调试配置文件中发生某些事情,这是您应该考虑的第一类错误。 In Release profile 99 % of the time uninitialized variable will be zero. 在发布配置文件中,未初始化变量的99%的时间将为零。 The compiler does help you here and in Debug profile it fills uninitialized memory with (non-zero) garbage, so it crashes for you in order to discover an otherwisely hidden bug. 编译器确实在这里为您提供了帮助,并且在“调试”配置文件中,它用(非零​​)垃圾填充了未初始化的内存,因此它崩溃了,以便发现原本隐藏的错误。

This suggests that you've got some sort of double-destruction of a buffer going on. 这表明您正在进行某种双重破坏的缓冲区。 You should have validation layers enabled while developing, because if you do so, they validation should catch this before it triggers a crash. 在开发过程中,应该启用验证层,因为如果这样做,它们的验证应该在触发崩溃之前就将其捕获。

In terms of figuring out what exactly is going wrong, the easiest way is probably to put logging in your move assignment operator, ctor and dtor (which includes the handle of the buffer being passed to vkDestroyBuffer ) so that you can see what the actual sequence of calls is. 就弄清楚到底出了什么问题,最简单的方法可能是将日志记录放入移动分配运算符ctor和dtor(包括传递给vkDestroyBuffer的缓冲区的句柄),以便您可以看到实际的序列的电话是。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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