C++默认参数通过_CrtMemDifference屏蔽内存泄漏检测

C++ default argument masks memory leak detection via _CrtMemDifference

本文关键字:屏蔽 内存 泄漏 检测 CrtMemDifference 默认 参数 C++      更新时间:2023-10-16

长期读者,第一次提问者。

我正在使用Visual Studio 2013中的Microsoft单元测试框架编写单元测试。我正在使用此答案 https://stackoverflow.com/a/2981185/4446293 中描述的内存泄漏检测代码的变体,效果很好。但是,我想命名 CrtCheckMemory 的每个实例,以便清楚地识别产生泄漏的测试用例,并提供默认测试名称。这是我的测试用例的完整源代码:

#include "stdafx.h"
#include "CppUnitTest.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
#include <string>
#include <ostream>
#include <crtdbg.h>
// A default test name, allocated statically so as to definitely not affect the heap.
static const char* UNNAMED_TEST = "Unnamed Test";
// Instances of this struct should detect heap differences that occur
// while they are in scope.
struct CrtCheckMemory {
_CrtMemState state1, state2, state3; 
const char* tname;
// Allowing this default argument to be used seems to
// mask this class's ability to see heap changes.
CrtCheckMemory(const char* testName = UNNAMED_TEST) : tname(testName) {
_CrtMemCheckpoint(&state1);
}
~CrtCheckMemory() {
_CrtMemCheckpoint(&state2);
if (_CrtMemDifference(&state3, &state1, &state2)) {
std::ostringstream oss; oss << "MEMORY LEAK DETECTED in " << tname << "- see Debug output for details";
Logger::WriteMessage(oss.str().c_str());
_CrtMemDumpStatistics(&state3);
}
}
};
namespace TestLeakyCauldron
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(DetectsALeak_Test) {
CrtCheckMemory checker("DetectsALeak");
malloc(1);
Logger::WriteMessage("You should see a MEMORY LEAK DETECTED message for test DetectsALeak");
}
TEST_METHOD(LeaksButNoMessage_Test) {
CrtCheckMemory checker();
malloc(1);
Logger::WriteMessage("You SHOULD see a MEMORY LEAK DETECTED message for test Unnamed Test, but you won't");
}
};
}

只要我实际向 CrtCheckMemory 构造函数提供一个名称,这就可以很好地工作。但是,如果我不提供名称,则不会检测到 CrtCheckMemory 对象在范围内时发生的泄漏(或者无论如何,不会向 VS2013 输出窗口的"测试"窗格发出"检测到内存泄漏"消息)。

我的问题是,为什么泄漏检测在一种情况下有效,而在另一种情况下无效?

我不明白为什么提供一个仅是静态分配的指针值的默认参数会改变 CRT 堆的状态。我猜这与 _CrtMemCheckpoint() 和 _CrtMemDifference() 实现的内部有关。但是我是否错过了一些关于 ctor 的默认参数如何工作的重要内容?在两种不同的 ctor 情况下,我没有看到任何堆活动的机会(默认参数与显式参数)。

注意:我最初使用 std::string成员来保存 CrtCheckMemory 类中的测试名称,但我改为使用 const string* 和字符串文字,以避免 std::string 构造函数和析构函数以 _CrtMemCheckpoint() 可见的方式更改堆状态的可能性。

发现了问题,这基本上是我花在 Java 上的时间比现在花在 Java 上的时间比C++多。

CrtCheckMemory crtcm();

不是使用默认 CTOR 声明实例的正确方法。那将是:

CrtCheckMemory crtcm;

正如Hans Passant指出的那样,这是C++"最令人烦恼的解析":Visual C++接受"不正确"的声明,因为它是不接受任何参数并返回CrtCheckMemory的函数的正确decl。它确实发出了我以前忽略的警告(C4930)。使用错误的 decl 时不会报告泄漏,因为在测试范围内实际上没有创建 CrtCheckMemory 实例。更正此问题会导致预期的泄漏检测消息。