【问题标题】:Debug Assertion Failed! Expression: __acrt_first_block == header调试断言失败!表达式:__acrt_first_block == 标头
【发布时间】:2016-05-20 12:06:45
【问题描述】:

我正在尝试测试我用 GoogleTest 编写的 dll,当我调用其中一个测试时,它会抛出这个错误:

我得出的结论是,问题在于将内存分配给向量,但我不知道如何解决这个问题,因为我对 C++ 编程还很陌生。代码如下:

#ArraysCPP11.h
#ifdef ARRAYSCP11_EXPORTS
#define ARRAYSCP11_API __declspec(dllexport)
#else
#define ARRAYSCP11_API __declspec(dllimport)
#endif

__declspec(dllexport) void removeWhiteSpaces(std::vector<std::string> v, std::vector<std::string> &output);
#ArraysCPP11.cpp
void removeWhiteSpaces(std::vector<std::string> v, std::vector<std::string> &output) { //odstranjevanje presledkov iz vector-ja (vsak drugi element je bil presledek)
    for (std::vector<std::string>::iterator it = v.begin(); it != v.end(); it++) {
        std::string buffer = *it;
        if (isdigit(buffer[0])){;
            output.push_back(*it);
        }
    }
}
#TestTemp.h

template<class T> 
class TestTemp
{
public:
   TestTemp();
   void SetValue(T obj_i);
   T GetValue();
   bool alwaysTrue();
   bool TestTemp<T>::formattingTest(std::string input, std::vector<std::string> realVector, std::vector<std::string> formattedInput);
private:
   T m_Obj;
};

template<class T>
inline bool TestTemp<T>::formattingTest(std::string input, std::vector<std::string> realVector, std::vector<std::string> formattedVector) {
std::string input2 = input;
//  std::vector<std::string> fResult;
std::string first;
std::string second;
bool endResult = true;
std::vector<std::string> end;
//std::vector<std::string> result = split(input2, ' ');
removeWhiteSpaces(formattedVector,end);
std::vector<std::string>::iterator yt = realVector.begin();
for (std::vector<std::string>::iterator it = end.begin(); it != end.end(); it++, yt++) {
    first = *it;
    second = *yt;
    if (first.compare(second) != 0) {
        endResult = false;
        break;
    }
}
return endResult;
}
   #ArraysCPP11-UnitTest.cpp
struct formattingTesting{
   //   formattingTesting* test;
   std::string start;
   std::vector<std::string> endResult;
   formattingTesting() {
   }
   explicit formattingTesting(const std::string start, const std::vector<std::string> endResult)
    : start{start}, endResult{endResult} 
   {
   }
};

struct fTest : testing::Test {
   formattingTesting* test;
   fTest() {
      test = new formattingTesting;
   }
   ~fTest() {
      delete test;
   }
};

struct format {
   std::string start;
   std::vector<std::string> end;
};

struct formTest : fTest, testing::WithParamInterface<format> {
   formTest() {
      test->start = GetParam().start;
      test->endResult = GetParam().end;
   }
};

TEST_P(formTest, test1) {
   bool endResult = true;
   TestTemp<int> TempObj;
   std::string first;
   std::string second;
   //std::string start ("1  2 3 4 5 6 7 8 9 10");
   //std::vector<std::string> end = { "1","2","3","4","5","6","7","8","9","10" };
   std::vector<std::string> start2 = { "1","","2","3","4","5","6","7","8","9","10" };
   std::string start = GetParam().start;
   std::vector<std::string> end = GetParam().end;
   bool result = TempObj.formattingTest(start,end,start2);      
   EXPECT_TRUE(result);
}

INSTANTIATE_TEST_CASE_P(Default, formTest, testing::Values(
   format{ "1", {"1"} },
   format{ " ", {} },
   format{ "1 2 3 4 5",{"1","2","3","4","5"} },
   format{ "1  2 3 4 5  6", {"1","2","3","4","5","6"} }
));


int main(int argc, char** argv)
{
   testing::InitGoogleTest(&argc, argv);
   RUN_ALL_TESTS();
   return 0;
}

【问题讨论】:

  • 您的代码中有未定义的行为start2 向量中有一个空字符串,您最终将其传递给 removeWhiteSpace 函数,您可以在其中访问向量中字符串的第一个字符。如果字符串为空,则它没有第一个字符,因此您的索引超出范围。 removeWhiteSpace 这个名字也不是很好,因为该函数实际上并没有删除空格,它只是检查字符串的第一个字符是否是数字(正如我刚刚指出的错误)。
  • @JoachimPilebog 向量start2 主要用于调试测试的比较部分(调用removeWhiteSpaces 之后的代码部分),将在我修复问题后删除.此外,removeWhiteSpaces 函数实际上确实删除了空白,因为它仅将数字元素推入新向量
  • 顺便说一句。为什么要全部复制向量而不是通过 const 引用传递它们(例如在removeWhiteSpaces(std::vector&lt;std::string&gt; v, ...) 中,您只查看向量 v 所以它不需要是非常量)。
  • @axalis 好吧,根据我的理解来评论你的例子(我是一个非常完整的 C++ 初学者)我使函数工作,所以向量 v 是一个输入,函数推动向量 output 中的数字值。我确实知道可能有更好的方法来处理这个问题,但我还没有真正找到一个,所以任何建议都会非常感激
  • 重点是您可以使用void removeWhiteSpaces(const std::vector&lt;std::string&gt; &amp; v, std::vector&lt;std::string&gt; &amp;output); 不复制第一个向量(并在内部使用std::vector&lt;std::string&gt;::const_iterator 而不是std::vector&lt;std::string&gt;::iterator)。其他一些不需要修改输入参数的函数也是类似的。

标签: c++ visual-studio-2015


【解决方案1】:

由于这是一个 DLL,问题可能在于用于分配和释放的不同堆(尝试静态构建库并检查是否可行)。

问题是,DLL 和模板不能很好地融合在一起。通常,根据 MSVC 运行时的链接,如果内存在可执行文件中分配并在 DLL 中释放,反之亦然(因为它们可能具有不同的堆),这可能会出现问题。模板很容易发生这种情况,例如:你 push_back() 到 DLL 中 removeWhiteSpaces() 内的向量,因此向量内存在 DLL 内分配。然后你在可执行文件中使用输出向量,一旦它超出范围,它就会被释放,但是在可执行文件中,它的堆不知道任何关于它被分配的堆的信息。砰,你死定了。

如果 DLL 和可执行文件都使用相同的堆,则可以解决此问题。为确保这一点,DLL 和可执行文件都必须使用动态 MSVC 运行时——因此请确保它们都动态链接到运行时,而不是静态链接。特别是,exe 应使用 /MD[d] 编译和链接,库也应使用 /LD[d] 或 /MD[d] 进行编译和链接,两者都不能使用 /MT[d]。请注意,之后将运行应用程序的计算机将需要 MSVC 运行时库才能运行(例如,通过为特定的 MSVC 版本安装“Visual C++ Redistributable”)。

即使使用 /MT,您也可以完成这项工作,但这更困难 - 您需要提供一些接口,以允许在 DLL 中分配的对象也可以在那里释放。例如:

__declspec(dllexport) void deallocVector(std::vector<std::string> &x);

void deallocVector(std::vector<std::string> &x) {
    std::vector<std::string> tmp;
    v.swap(tmp);
}

(但是这在所有情况下都不能很好地工作,因为这需要显式调用,因此不会被调用,例如在异常情况下 - 要正确解决此问题,您需要从 DLL 提供一些接口,这将覆盖引擎盖下的矢量,并注意正确的 RAII)


编辑:最终的解决方案实际上是将所有项目(exe、dll 和整个 googleTest 项目)内置在 多线程调试 DLL (/MDd) (GoogleTest 项目默认内置多线程调试(/MTd))

【讨论】:

  • 我将尝试这样做,但遗憾的是最终我需要找到一种方法让它像我的主管想要的那样作为 DLL 工作
  • 更新了答案,你基本上需要链接到动态运行时以避免此类问题。
  • 所以如果我理解正确,我需要将 exe 的运行时库设置为 /MD,将 DLL 的运行库设置为 /LD 对吗?如果是这样,我真的不能为 DLL 这样做,因为 /LD 选项没有提供给我
  • 嗯...我认为您也可以将 /MD 用于库,/LD 只是提供了一些额外的自动宏定义 AFAIK(也许它在较新的版本中被删除了,我没有在 MSVC 2013 中也可以看到)
  • 在 /MD 中构建 exe 会导致我收到一堆链接器错误(直到现在我一直在 /MT 中构建它并且它是正确的)
【解决方案2】:

我遇到了类似的问题,结果发现我的 unittest 项目被设置为不同的代码生成运行时库 - 所以通过将其设置为与 DLL 项目相同,然后没有堆异常

【讨论】:

    【解决方案3】:

    该验证是很久以前由 Microsoft 软件开发人员在 1992 - 1993 实现的,它不再有效,因为在异构或 MPI 编程的情况下,新内存可能不是从本地分配的堆。

    当应用程序使用 OpenCL 或 CUDA API 获取内存时,GPU 驱动程序会执行所有内存分配,当然,它不会使用应用程序的本地堆。但是,应用程序应该在退出之前释放内存。那时,Microsoft 的 Memory Leaks Detection API 会检测到它并显示该断言。

    请查看有关验证来源的视频技术报告:

    MS Visual Studio 2015 Assert __acrt_first_block == header (VTR-010) 的起源 https://www.youtube.com/watch?v=NJeA_YkLzxc

    注意:YouTube 视频的网络链接已更新,因为我上传了一个包含一些更正的视频。

    【讨论】:

    • 视频链接无效,提示视频不可用!
    【解决方案4】:

    我遇到了同样的错误,我找到了一种方法来获取有关问题原因的更多信息:可以使用 Visual Studio 在引发此错误的行上设置 breakpoint condition(以便调试器中断 在错误消息之前)。

    需要打开相应的文件(debug_heap.cpp,在“C:\Program Files (x86)\Windows Kits\10\Source”中的某处)并写下如下条件:

    然后我们可以继续调试,当断点被命中时,我们可以观察引发错误的块的地址(包含断点的“free_dbg_nolock”函数的“block”参数)。

    从那里您可以通过将地址复制到内存窗口(Debug->Windows->Memory)来观察块的内存内容。如果幸运的话,它可能是一个字符串或一个易于识别的变量。

    然后您可以识别导致错误的变量并尝试修复它。

    在我的例子中,它是一个在一个 dll 中创建并在另一个 dll 中删除的变量。为了纠正它,我用一个 dll 中的这些对象的指针替换了我的所有对象。

    【讨论】:

      【解决方案5】:

      我在使用 Microsoft Visual Studio 2019 时遇到了同样的问题,只需将“运行时库”更改为“多线程调试 DLL (/MDd)”即可解决

      在“解决方案资源管理器”->“属性”->“C/C++”->“代码生成”上右键单击解决方案,并将“运行时库”更改为“多线程调试DLL (/MDd)”

      【讨论】:

        【解决方案6】:

        我也看到了这个错误,我的所有内存模型设置都正确排列。但是最近将项目从 vs2013 升级到 vs2015 我在 .exe 和 .dll 之间有过时的引用,所以实际上我使用的是用 2013 构建的旧 DLL。我不得不删除 .exe 和 .dll 之间的引用并重新-添加它以更新 exe 链接的 .lib 的名称。 (右键单击 .exe 项目的“引用”子项并“添加”,同时混淆还允许您删除引用)。

        【讨论】:

          猜你喜欢
          • 2013-06-06
          • 2018-03-23
          • 1970-01-01
          • 1970-01-01
          • 2010-11-09
          • 2016-08-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多