【问题标题】:Exception thrown at 0x0037A5C2 project.exe: 0xC0000005: Access violation writing location 0xDDDDDDDD at the end of the program在 0x0037A5C2 project.exe 处抛出异常:0xC0000005:程序末尾的访问冲突写入位置 0xDDDDDDDD
【发布时间】:2021-03-06 12:08:27
【问题描述】:

我通过简单地创建指定类的实例在程序的最后遇到了这个运行时异常,所以我认为问题出在构造函数、复制构造函数、复制赋值运算符或析构函数上。在我有限的 cpp 知识范围内,我已经阅读并遵循了三法则。

来源.cpp

#include "Header.h"
#include <iostream>

using namespace std;


int main() {


string command = "CREATE TABLE table_name IF NOT EXISTS ((column_1_name,type,default_value), (column_2_name,type,default_value))";
string columns[20] = { "column_1_name,type,default_value", "column_1_name,type,default_value" };
string commandData[9] = { "table_name", "IF NOT EXISTS" };

CommCREATETABLE comm(command, columns, commandData, 2, 2);

}

Header.h 中的相关代码

class CommCREATETABLE {
    string fullCommand = "";
    string* columns = nullptr;
    string* commandData = nullptr;
    string tableName = "";
    int nrOfColumns = 0;
    int nrOfElements = 0;
    bool valid = false;

构造函数:

CommCREATETABLE(string command, string* columns, string* commandData, int nrOfRows, int nrOfElements) {
        this->setNrOfColumns(nrOfRows);
        this->setNrOfElements(nrOfElements);
        this->setCommand(command);
        this->setColumns(columns);
        this->setCommandData(commandData);
        this->valid = checkInput(this->commandData, this->columns);
        this->setTableName(commandData[0]);
    }

拷贝构造函数、拷贝赋值运算符、析构函数:

CommCREATETABLE(const CommCREATETABLE& comm) {
        this->setNrOfColumns(comm.nrOfColumns);
        this->setNrOfElements(comm.nrOfElements);
        this->setCommand(comm.fullCommand);
        this->setColumns(comm.columns);
        this->setCommandData(comm.commandData);
        this->setTableName(comm.tableName);
        this->valid = comm.valid;
    }
    ~CommCREATETABLE() {
        if (this->columns != nullptr) {
            delete[] this->columns;
        }
        if (this->commandData != nullptr) {
            delete[] this->commandData;
        }
    }

    CommCREATETABLE& operator=(const CommCREATETABLE& comm) {
        this->setCommand(comm.fullCommand);
        this->setColumns(comm.columns);
        this->setCommandData(comm.commandData);
        this->setTableName(comm.tableName);
        this->setNrOfColumns(comm.nrOfColumns);
        this->setNrOfElements(comm.nrOfElements);
        this->valid = checkInput(this->commandData, this->columns);
        return *this;
    }

处理动态内存分配的唯一setter如下:

void setColumns(const string* columns) {
        if (this->nrOfColumns >= 0) {
            this->columns = new string[this->nrOfColumns];
            memcpy(this->columns, columns, this->nrOfColumns * sizeof(string));
        }
        else throw EmptyCommandException();
    }
    void setCommandData(const string* commandData) {
        if (this->nrOfElements >= 0) {
            this->commandData = new string[this->nrOfElements];
            memcpy(this->commandData, commandData, this->nrOfElements * sizeof(string));
        }
        else throw EmptyCommandException();
    }

【问题讨论】:

  • 0xDDDDDDDD 在 Visual Studio 中意味着您正在取消引用指向已释放堆内存的指针:https://stackoverflow.com/questions/127386/in-visual-studio-c-what-are-the-memory-allocation-representations
  • 我会避免像现在这样使用memcpy(最后一对方法)。 string 类内部包含一个指针,您现在正在做的是复制该指针。这意味着当原始字符串被删除时,指针不再有效。但是,您的班级仍然拥有相同的指针。因此,当您的类被删除时,它会再次尝试删除该指针。这显然会导致问题。相反,只需使用= 复制数组中的每个字符串,循环遍历所有字符串,即可解决问题。

标签: c++ constructor destructor copy-constructor memcpy


【解决方案1】:

快速浏览一下,我会说问题出在您的 setColumnssetCommandData 函数中。 (我当然可能是错的,我没有尝试运行您提供的代码,也没有尝试运行我所做的更改——因此也可能在某处出现拼写错误。)

您使用memcpy 将字符串复制到您的类中。但是,在 C++ 内部,string 持有指向实际字符串的指针,因此使用 memcpy 实际上只复制该指针。结果,一旦原始字符串被删除,您复制到类中的指针就不再有效(因为内存已经被释放)。结果,一旦您的类也被删除,它就会尝试删除已释放的内存。这可能就是您的错误的来源。

事实上,如果你在程序中添加了试图操作类的行(在原始输入字符串已经被删除之后),问题会更快出现,因为你将访问已经被删除的内存释放。这会导致未定义的行为,通常以崩溃告终。

一个快速的解决方法是更改​​复制数据的方式,通过为每个字符串使用=(以这种方式将实际字符串复制到内存中的新位置,而不是复制指针)。

void setColumns(const string* columns) {
    if (this->nrOfColumns > 0) { // Creating an array of size 0 is also not a good idea. 
        this->columns = new string[this->nrOfColumns];
        for (int i = 0; i < nrOfColumns; i++) { // You don't need this everywhere. 
            this->columns[i] = columns[i]; 
            // I don't think naming things the exact same way is good practice. 
        }
    }
    else throw EmptyCommandException();
}
void setCommandData(const string* commandData) {
    if (this->nrOfElements > 0) { // Creating an array of size 0 is also not a good idea. 
        this->commandData = new string[this->nrOfElements];
        for (int i = 0; i < nrOfElements; i++) { // You don't need this everywhere. 
            this->commandData[i] = commandData[i]; 
            // I don't think naming things the exact same way is good practice. 
        }
    }
    else throw EmptyCommandException();
}

或者,如果您想避免复制,您应该查看move,但如果您仍在学习,我建议您暂时不要这样做。你很快就会到达那里。

【讨论】:

  • 泛化:memcpy只能在被复制的对象是Trivially Copyabvle时使用
  • 旁注:如果允许,use std::vector 而不是动态分配的数组。节省时间,通常会大量调试。
  • 最好将任何memcpy 替换为std::copy 而不是for 循环。在这种情况下,您会得到正确的行为,并且对于可简单复制的类型,它可能会编译为快速的 memmove
猜你喜欢
  • 2020-02-13
  • 2017-01-27
  • 1970-01-01
  • 2021-06-04
  • 1970-01-01
  • 2012-12-05
  • 1970-01-01
  • 1970-01-01
  • 2020-06-17
相关资源
最近更新 更多