【问题标题】:Why is vector deleting destructor being called as a result of a scalar delete?为什么标量删除会调用向量删除析构函数?
【发布时间】:2011-03-23 08:08:12
【问题描述】:

我有一些代码在大型系统中崩溃。 但是,代码本质上归结为以下伪代码。 我已经删除了很多细节,因为我试图将其归结为裸露的骨头; 不过,我认为这并没有遗漏任何重要的内容。

// in a DLL:

#ifdef _DLL
#define DLLEXP __declspec(dllexport)
#else
#define DLLEXP __declspec(dllimport)
#endif

class DLLEXP MyClass // base class; virtual
{
public:
  MyClass() {};
  virtual ~MyClass() {};

  some_method () = 0; // pure virtual

  // no member data
};

class DLLEXP MyClassImp : public MyClass
{
public:
  MyClassImp( some_parameters )
  { 
    // some assignments...
  }

  virtual ~MyClassImp() {};

private:
  // some member data...
};

和:

// in the EXE:

MyClassImp* myObj = new MyClassImp ( some_arguments ); // scalar new
// ... and literally next (as part of my cutting-down)...
delete myObj; // scalar delete

请注意,正在使用匹配的标量 new 和标量删除。

在 Visual Studio (2008 Pro) 的调试版本中, 在微软的 中, 以下断言失败:

_ASSERTE(_CrtIsValidHeapPointer(pUserData));

堆栈顶部附近有以下项目:

mydll_d.dll!operator delete()
mydll_d.dll!MyClassImp::`vector deleting destructor'()

我觉得应该是这样的

mydll_d.dll!MyClassImp::`scalar deleting destructor'()

也就是说,程序的行为就像我写的一样

MyClassImp* myObj = new MyClassImp ( some_arguments );
delete[] newObj; // array delete

pUserData 中的地址是myObj 本身的地址(而不是成员)。 该地址周围的内存如下所示:

                                ... FD FD FD FD
(address here)
VV VV VV VV MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
FD FD FD FD AB AB AB AB AB AB AB AB EE FE EE FE
...

其中四个VV想必是虚函数表的地址, MM...MM 是可识别的成员数据, 其他字节是调试器放置的各种特殊标记 (例如,FD FDs 是对象存储周围的“保护字节”)。

在断言失败前不久,我确实看到了 VVs 的变化, 并想知道这是否是由于切换到基类的虚函数表所致。

我知道正在销毁的类层次结构中的错误级别的问题。 这不是这里的问题。我的析构函数都是虚拟的。

我注意到微软的页面 “BUG:为导出的类调用了错误的运算符删除” http://support.microsoft.com/kb/122675 但这似乎与试图对破坏数据负责的错误可执行文件(使用错误的堆)有关。

在我的情况下,删除析构函数的错误“风味”似乎正在被应用: 即向量而不是标量。

我正在尝试生成仍然存在问题的最小缩减代码。

但是,任何有助于进一步调查此问题的提示或技巧将不胜感激。

也许这里最大的线索是堆栈上的mydll_d.dll!operator delete()。 我应该期望这是myexe_d.exe!operator delete(), 表示DLLEXPs 已经“丢失”了?

我想这可能是双重删除的一个实例(但我不这么认为)。

关于_CrtIsValidHeapPointer 检查的内容,我是否可以阅读一个很好的参考资料?

【问题讨论】:

  • 在 MyClassImp 中,ctor 和 dtor 可能被称为 MyClassImp 而不是 MyClass,不是吗?
  • 哦,你可以在 new() 中按 F11 吗?是哪一个?
  • @Calvin1602 [1] 谢谢,是的——我现在已经更正了。 [2] 被调用的 c'tor 是 MyClassImpMyClass 被调用之后/期间的那个。
  • 一旦您开始从 DLL 导出类,使用 /MD 编译就变得非常重要。在我看来像 /MT。
  • @Hans - 这也是一个线索。也许您应该将其添加为答案,而不仅仅是评论。

标签: c++ debugging visual-c++ msvcrt destructor


【解决方案1】:

听起来这可能是从一个堆中分配并尝试在另一个堆上删除的问题。当从 dll 分配对象时,这可能是一个问题,因为 dll 有自己的堆。从您展示的代码来看,这似乎不是问题所在,但在简化过程中可能丢失了一些东西?在过去,我看到像这样的代码使用工厂函数和对象上的虚拟destroy 方法来确保分配和删除发生在 dll 代码中。

【讨论】:

  • 这当然是答案的关键,汉斯对这个问题的评论也是如此(对此,另请参阅我自己的“答案”)。碰巧的是,完整的代码(而不是我的精简代码)确实有 destroy() 方法,以及创建工厂。尽管如此,构建设置中的其他错误仍然导致所描述的问题。
【解决方案2】:

Microsoft 为其 C 运行时提供源代码;您可以在那里查看_CrtIsValidHeapPointer 的作用。在我的安装中,它位于C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\dbgheap.c

另一个建议是检查反汇编

delete newObj; // scalar delete

并将其与为生成的反汇编进行比较

delete[] newObj;

delete pointerToClassLikeMyClassThatIsInExeAndNotDll;

测试你关于delete[] 被调用的理论。同样,您可以检查调用堆栈

delete pointerToClassLikeMyClassThatIsInExeAndNotDll;

测试您关于 mydll_d.dll!operator delete()myexe_d.exe!operator delete() 的理论。

【讨论】:

    【解决方案3】:

    感谢您的所有回答和 cmets。 所有这些都是有用且相关的。

    仍然欢迎任何进一步的信息。


    以下是 Hans Passant 对我的问题的评论:

    一旦您开始从 DLL 导出类, 用 /MD 编译变得非常重要。 在我看来像 /MT。

    因此,我仔细研究了整个项目的链接设置。 我发现了一个“隐藏”的 /MT 和 /MTd 实例,它应该是 /MD 和 /MDd, 加上其他设置中的一些相关不一致。

    纠正了这些, 现在没有抛出任何断言,并且代码似乎表现正确。


    以下是在执行过程中遇到崩溃或断言失败时要检查的一些事项,从而使作用域和析构函数被调用。 确保贯穿所有项目(包括依赖项) 并且在所有配置中(尤其是在有问题的配置中):

    (这里的 *.vcproj 路径是相对于 /Configurations/Configuration/> 的。)

    • 中选择了正确的运行时 C/C++ |代码生成 |运行时库 ;
    • 适当的定义(如果有的话)在 C/C++ |预处理器 |预处理器定义 尤其是关于静态vs动态库的使用 (例如,_STLP_USE_STATIC_LIB 与 STLport 的 _STLP_USE_DYNAMIC_LIB);
    • 在中选择了适当版本的库 链接器 |输入 |附加依赖项 特别是与静态运行时库 DLL 的“包装器”有关 (例如 stlport_static.lib 与 stlport.N.M.lib)。

    有趣的是,我希望删除的 标量“风味”似乎仍然没有被调用(断点永远不会被命中)。 也就是说,我仍然只看到 vector 删除析构函数。 因此,这可能是一个“红鲱鱼”。

    也许这只是微软的实施问题, 或者也许还有一些我错过的其他微妙之处。

    【讨论】:

      【解决方案4】:

      此行为对于 MSVC 9 是特殊的,其中隐式生成具有虚拟析构函数的导出类的删除运算符,并使用关联标志将其修改为向量 dtor,其中 1 表示(标量),3 表示(向量) .

      这个东西的真正问题是,它打破了新/删除的规范形式,客户端编码器无法在其代码中禁用向量删除运算符,如果他认为,这是一个坏主意使用它。

      此外,向量 dtor 似乎也执行错误,如果 new 分配在另一个模块中,而不是实现所在的模块,然后通过引用计数保存在静态变量中,该引用计数执行删除 this(此处矢量 dtor 开始发挥作用)在进程关闭时。

      这与前面已经提到的堆问题“bshields”相匹配,dtor 在错误的堆上执行,并且代码在关闭时出现“无法读取该内存位置”或“访问冲突”崩溃 - 这些问题似乎是很常见。

      解决此错误的唯一方法是禁止使用虚拟析构函数并自己执行它,方法是强制使用基类中的 delete_this 函数 - 愚蠢的是你模仿虚拟 dtor 应该做的事情为你做。然后标量 dtor 也被执行,并且在关闭模块之间共享的任何引用计数对象,都可以以安全的方式实例化,因为堆总是正确地寻址到源模块。

      要检查,如果你有这样的问题,只需禁止使用向量删除运算符。

      【讨论】:

        【解决方案5】:

        就我而言,这是删除析构函数的错误“风味” 似乎正在应用:即向量而不是标量。

        这不是这里的问题。根据Mismatching scalar and vector new and delete 中的伪代码,scalar deleting destructor 只需调用vector deleting descructor,并带有一个标记为“执行标量破坏而不是矢量破坏”。

        正如其他海报所指出的,您的实际问题是您在一个堆上分配,而在另一个堆上删除。最明确的解决方案是让您的类重载 operator newoperator delete,正如我在类似问题的回答中所述:Error deleting std::vector in a DLL using the PIMPL idiom

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-04-27
          • 2012-02-12
          • 2017-01-10
          • 1970-01-01
          • 2015-01-15
          • 2012-03-13
          • 2014-05-27
          • 1970-01-01
          相关资源
          最近更新 更多