【问题标题】:C++ Struct with pointers : constructor and destructor带指针的 C++ 结构:构造函数和析构函数
【发布时间】:2012-10-15 17:44:14
【问题描述】:

我在调用 DLL 以执行优化任务的模拟程序时遇到问题。在研究了这个问题一段时间后,我认为我的问题在于我在 DLL 返回所需信息后用于释放内存的析构函数。仿真程序是在Borland C++ Builder v6上开发的,DLL是在MS Visual C++ 2005上开发的。

为了模拟程序 (P) 和 DLL 交换数据,我创建了两个结构 InputCPLEXOutputCPLEX 和一个函数 optimize,它接受两个参数:一个类型为 InputCPLEX 的对象和一个类型为输入OutputCPLEX。这两个结构都在属于 P 项目和 DLL 项目的头文件structures.h 中声明。

InputCPLEXOutputCPLEX 结构都有 intint* 成员,所以基本上文件 structures.h 看起来像:

//structures.h
struct InputCPLEX{
  public:
  int i;
  int* inputData;
}
struct OutputCPLEX{
  public:
  int j;
  int* outputData;
}

想法是沿着模拟过程(P的执行),我周期性地调用DLL来解决一个优化问题,所以inputData是我的优化问题中的变量对应的数组,outputData是我的变量的最佳值数组。我知道使用 STL 容器会更容易,例如vector<int>,但是 - 如果我错了,请纠正我 - 在两个不同的编译器之间交换 STL 对象似乎很困难。

这是我的主文件(在 P 中)中的样子:

//main.h
InputCPLEX* input;
OutputCPLEX* output;
int* var;
int* sol;

//main.cpp    
[...] //lots of code
input = new InputCPLEX;
output = new OutputCPLEX;
int n = X; //where X is an integer 
var = new int[n]; 
[...] //some code to fill var
input->i = n; 
input->inputData = var; 
optimize(input,output); //calls the DLL
int m = output->j; 
sol = new int[n];
sol = output->outputData;
[...] //some code to use the optimized data
delete[] var;
delete[] sol;
delete input;
delete output;
[...] //lots of code

一年多以来,我一直在使用此代码,而文件structures.h 中没有任何构造函数或析构函数,因此没有执行结构成员的初始化。您可能已经猜到了,我不是 C++ 专家,事实上恰恰相反。我还想强调一下,我没有编写大部分模拟程序,只是一些功能,这个程序是由几个开发人员开发了 10 多年,结果相当混乱。

但是,直到最近,一切都运行良好。我决定向 DLL 提供更多信息(出于优化目的),因此在运行大型模拟(涉及大型数据集)时,模拟程序会系统性地崩溃。额外的信息是两个结构中的指针,我的猜测是程序正在泄漏内存,所以我尝试编写构造函数和析构函数,以便可以正确管理分配给结构 inputoutput 的内存。我尝试了以下代码,我发现在互联网上搜索:

//structures.h
struct InputCPLEX{
  public:
  int i;
  int* inputData;
  int* inputData2; // extra info
  int* inputData3; // extra info
  InputCPLEX(): i(0), inputData(0), inputData2(0), inputData3(0) {}
  ~InputCPLEX(){ 
    if (inputData) delete inputData;
    if (inputData2) delete inputData2;
    if (inputData3) delete inputData3;
  }
}
struct OutputCPLEX{
  public:
  int j;
  int* outputData;
  int* outputData2;
  int* outputData3;
  OutputCPLEX(): j(0), outputData(0), outputData2(0), outputData3(0) {}
  ~OutputCPLEX(){ 
    if (outputData) delete outputData;
    if (outputData2) delete outputData2;
    if (outputData3) delete outputData3;
  }
}

但它似乎不起作用:程序崩溃得更快,只用了很短的时间。有人可以帮我找出代码中的问题吗?我知道可能还有其他因素影响我的程序的执行,但是如果我将structures.h文件中的构造函数和析构函数都删除了,那么模拟程序仍然能够执行小型模拟,涉及到小数据集。

非常感谢您的帮助, 大卫。

【问题讨论】:

  • delete 在空指针上无效。析构函数中的ifs 是不必要的。你应该关注rule of three
  • 你好,空指针上的>delete是什么意思没有效果你的意思是当我删除对象inputoutput时它会自动删除它们的成员,所以我不需要在析构函数中声明?
  • 表示删除前不必检查指针是否为NULL。
  • 好的,我明白了,我将删除ifs。谢谢

标签: c++ dll struct destructor


【解决方案1】:

你必须使用一致的方式新建 - 删除。如果new[] 获得了某些东西,您应该通过delete[] 将其删除,如果通过new -> 则通过delete 删除。在您的代码中,您通过new 创建inputoutput,但通过delete[] 删除。

顺便说一句,您不必在删除之前检查指针是否为零。 delete 处理零指针没有问题。

【讨论】:

  • 感谢您的及时回复,我发布的代码中有错误:我确实使用delete 而不是delete[] 删除了对象inputoutput。我更正了我原来的帖子。
【解决方案2】:

我在您的代码中发现了几个问题:

1) 内存泄漏/双重删除:

sol = new int[n];
sol = output->outputData; 

在这里,您在初始化后立即覆盖sol 指针,new int[n] 分配的数据被泄露。您还可以在sol 中双重删除指针 - 第二次在output 的析构函数中。 var 也有同样的问题 - 你删除了两次,通过显式 delete[]input 的析构函数。

使用delete添加析构函数后出现双重删除问题,看起来之前没有问题。

正如@Riga 提到的,您使用new[] 分配数组,但在析构函数中使用delete 而不是delete[]。这是不正确的,这是未定义的行为。尽管这看起来不像是崩溃的原因。在现实世界中,大多数编译器对内置和 POD 类型实现 deletedelete[] 并没有什么不同。只有当您 delete 具有非平凡析构函数的对象数组时才会出现严重问题。

2) output->outputData 分配在哪里?如果在 DLL 中,这是另一个问题,因为如果在使用另一个编译器实现的 DLL 中分配内存,您通常无法在主程序中安全地释放内存。原因是new/delete实现不同,主程序和DLL运行时使用的堆不同。

您始终应在同一侧分配/取消分配内存。或者使用一些常见的低级 API - 例如VirtualAlloc()/VirtualFree()HeapAlloc()/HeapFree() 具有相同的堆句柄。

【讨论】:

  • 关于你的第一点:我将删除第一个数据分配sol = new int[n];。但是,我不确定我是否理解双重删除:varsol 指针不是结构 InputCPLEXOutputCPLEX 的成员。因此,它们不会被显式delete[] 删除两次,而只会删除一次。我不明白为什么input 的析构函数会删除var。至于@Riga 和你的建议,我已经更正了inputoutput 的删除,但我应该删除析构函数中的delete 指令吗? (从你告诉我的我认为我应该)。
  • 关于您的第二点,您的猜测是正确的:output->outputData 分配在 DLL 中。但是我可以在使用优化功能之前通过用虚拟值填充outputData 来分配它,我应该这样做吗?
  • @David 是的,它们不是结构的成员,但它们拥有与您分配的相同的指针值。指针的赋值复制指针的值(它指向的地址),而不是它指向的结构。你可以有很多指针指向内存中的相同结构,但是只有一个可以用来删除它。这被命名为所有权
  • @David 关于第二点 - 您应该在主程序中分配和删除您传递给 DLL 的内存,这是正确的。你说的虚拟值是什么意思,零填充outputData指向的分配数组?
  • 是的。感谢您对指针的见解。如果我理解正确,我可以删除析构函数(或将它们留空)并明确删除指针 solvar?
【解决方案3】:

这看起来很奇怪:

int m = output->j; 
sol = new int[n];
sol = output->outputData;

据我了解,您以 m 为单位返回大小,但使用 n 分配 然后通过将指针 (sol) 设置为 outputData 来覆盖数组 我想你的意思是这样的:

int m = output->j; 
sol = new int[m];
memcpy(sol,output->outputData,sizeof(int)*m);

【讨论】:

  • 对不起,m 整数只是为了示例。我的意思是sol = new int[n];,即我想分配一个大小为n的数组。
猜你喜欢
  • 2016-06-30
  • 1970-01-01
  • 2022-01-20
  • 2022-01-01
  • 2011-04-03
  • 2010-12-16
  • 1970-01-01
  • 2012-09-27
  • 2017-06-10
相关资源
最近更新 更多