C++ for-each 语句触发“向量迭代器不兼容”断言失败:this->_Getcont() == 0

Posted

技术标签:

【中文标题】C++ for-each 语句触发“向量迭代器不兼容”断言失败:this->_Getcont() == 0【英文标题】:C++ for-each statement triggers "vector iterators incompatible" assertion failure: this->_Getcont() == 0 【发布时间】:2014-02-21 02:04:09 【问题描述】:

这是 Visual Studio 2012 的。

static void func(
  ...,
  const std::vector<std::string> &opt_extra_args_strs,
  ...)

   // THIS ASSERTS: "vector iterators incompatible"
   for (const std::string &arg_str : opt_extra_args_strs) 
      ... body does not modify opt_extra_args_strs

   // BUT THIS WORKS:
   for (size_t a_ix = 0; a_ix < opt_extra_args_strs.size(); a_ix++) 
       const std::string &arg_str = opt_extra_args_strs[a_ix];

我根本没有在循环体中修改向量,事实上,断言发生在第一次迭代之前。该向量在调试器中看起来正确,但我对 STL 了解不足,无法查找损坏。在 STL 内部,断言失败来自:

void _Compat(const _Myiter& _Right) const 
    // test for compatible iterator pair
    if (this->_Getcont() == 0 // THIS FAILS (_Getcont() == 0)
         ...) 
        _DEBUG_ERROR("vector iterators incompatible");

this-&gt;_Getcont() 为 NULL,因为(_Myproxy_Iterator_base12 中为 NULL)。 调用栈是:

msvcp110d.dll!std::_Debug_message(const wchar_t * message, const wchar_t * file, unsigned int line) Line 15 C++
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::_Compat(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::operator==(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::operator!=(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!run_test(..., const std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > & opt_extra_args_strs)
    ...

我怀疑设置向量的代码以某种方式搞砸了,但我不确定。我也很难编写一个更简单的复制器,但是程序应该是完全确定的(单线程,而不是随机变量,总是断言)。

此外,我之前也遇到过类似的断言失败 "vector iterator + offset out of range" 与 sn-p(在同一个向量上)

template <typename T>
class Elsewhere 
    virtual void earlier(
        ....
        std::vector<T> &v) const
    
       v.emplace_back(); // empty construction of a T
      // T &t = v.back(); // assertion failure
      T &val = to[to.size() - 1]; // but this works
      ... mutates val.

T = std::string(其实是同一个向量)。

我提到这一点是因为在 STL 中,此失败的条件最终也是 this-&gt;_Getcont() == 0,我怀疑它们是相关的。 _Getcont()Vector_const_iterator 中为 0 是什么意思?

向量来自容器

template <typename T>
struct type 
   T m_value;

   operator const T &() const 
     return value();
   

   const T &value() const 
       return m_value;
   
;

type<std::vector<std::string>> &t = ... method call that returns ref to it;
... t gets set
func(t); // implicit conversion to (const std::vector<std::string> &)

【问题讨论】:

&amp;opt_extra_args_strs: 是参考吗? 我添加了一些调用代码来说明它的来源。 【参考方案1】:

我终于找到了问题所在。向量设置代码中的一条路径正在破坏向量的状态(作为更大内存块的一部分,将其设置为 0)。这不会对向量中的前三个字段造成伤害:_Myfirst_Mylast_Myend,因为这些字段在初始向量中为 0。此外,诸如数组索引运算符和其他方法(如 push_back)之类的大多数东西仍然可以正常工作。但是,第四个字段_Myproxy 最初是非零的,清除它会禁用基于迭代器的功能。因此,for-each 循环、vector::back() 和其他循环都因不同的错误错误而失败,例如错误的边界检查、错误的不兼容迭代器等......

【讨论】:

这可能意味着您在迭代时修改了向量,这是无效的。 @BillyONeal 问题指出“我根本没有在循环体中修改向量,事实上,断言发生在第一次迭代之前。”如果它发生在循环体进入之前,循环体就不是问题。也就是说,我很想知道可能是什么问题。与 OP 不同,我真的不觉得这个答案令人满意:“something is corrupting memory”是找到答案的第一步(这是一个很好的步骤,我知道它可能很难找到) ,但 IMO 的实际答案是找出 什么 正在破坏内存、原因以及您应该采取哪些措施来避免它。 @hvd。我完全同意,这就是我为找到这个解决方案所做的。我的问题旨在寻找那个“东西”并希望其他人看到这些症状。无论如何,我确实找到了问题,这就是答案。如果您将有效的空 stl::vector 的内存归零(在调试版本中,VS2012+ 等),push_back 和索引操作仍然可以工作,但基于迭代的操作(foreach 和 back())将因不直观而失败错误(如所述)。 @Tim 啊,现在我明白了,您的意思是设置向量的代码正在使用 memset。我以为您的意思是向量自己的方法正在使用 memset (由于某处的无效调用),我没有按照您的意图阅读“向量的设置代码”。在这种情况下,很好的发现和很好的答案。 :)【参考方案2】:

关于 range for 循环的第一项,除非 C++11 标准中有新内容,否则不兼容的迭代器消息是 100% 准确的。该行试图将常量字符串引用分配给向量中的迭代器。迭代器类型与所存储元素的数据类型不兼容。

请查看http://www.stroustrup.com/C++11FAQ.html#for。在该示例中,使用了 auto 数据类型,以便在容器的开头设置通过冒号使用的迭代器的底层设置(例如,比过去少得多的键盘输入)。然后在幕后发生了更多确保迭代器永远不会传递最后一个元素的事情。上面 for 循环的等价范围(如所写)是:

// added a local string to clearly indicate types
std::string s1;
const std::string &arg_str = s1;


const std::vector<std::string> :: iterator i;
i = opt_extra_args_strs.begin(); // happens inside the range for

for (arg_str = i; i < opt_extra_args_strs.end(); i++)

   // loop body

只要将 arg_str 引用重新分配给迭代器的起点,编译器就会报错并打印错误。

第二个 for 循环是避免迭代器的较旧替代方法,但继续使用可用于范围检查动态容器(如矢量)的其他方法,并保持在容器边界内以获取当前在其中的元素数量。该循环必须始终有效,因为循环体为容器内的每个元素(也是一个字符串,但不是一个常量字符串)分配了一个本地分配的 const 字符串引用。永远不会尝试将迭代器类型分配给第二个 for 循环体内的字符串引用。

这个新的 range for 具有许多不错的功能,可以尽可能减少键盘输入。但是,最好使用编译器始终正确的 auto 关键字和后续数据类型分配(假设它永远保持 C++11 合规性)。

断言失败

T &t = v.back(); // assertion failure

对于空向量也完全正确(开始编辑)

对我来说,back() 方法是最近添加到 STL 的。对不起。在出发前匆忙完成原始帖子,我阅读了单词 back() 并将其在我的大脑中翻译为 end()。

如果调用了 end() 方法: T &t 与 std :: vector&lt;T&gt; :: iterator 的类型不同,这是调用 v.end() 时返回的内容。就类型而言,它看起来更像这样:

 T &t = std::vector<T> :: iterator

查看 std::vector::back() 的详细信息后,如果对空向量调用 back(),则行为未定义。更有可能的是,仅在运行时才能发现缺陷。作为参考,试试这个:http://www.cpluscplus.com/reference/vector/vector/back。对于总是调用 back(),首先要确认的是向量中至少存在一个元素。在此之前,由于emplace_back(Args&amp;&amp;... args); 是http://www.cpluscplus.com/reference/vector/vector/emplace_back 处的原型,因此没有定义在当前最后一个元素之后插入的不带参数调用它。参考页面说,“如果适当的参数不支持 allocator_traits::construct,它会导致 未定义的行为。”代码可能必须变得更像:

//
// start of the 'earlier' function body 
//
std::string s;
v.emplace_back(s); // add one element to v

//
// obtain a reference to the last element (should be a copy of s above)
//
T &t = v.back(); 



//
// It is not clear what the vector 'to' is and how it exists inside 'earlier'
// as long as 'to' has at least one element, then the code below will 
// set the local reference variable to the last element of 'to'.  
// If not, then another run time error is likely with attempting to access to[-1]
// and then attempting to assign the non-existent element to T& val
//
T &val = to[to.size() - 1];

我希望这有助于理解迭代器、在当前最后一个元素之后添加一个元素以及存储在容器中的数据元素类型之间的区别。

(结束编辑)

内存分配区域出现问题,这将是非常令人惊讶的。 Visual Studio 2012 会有很多不开心的 C++ 工程师。强烈建议撤消 stl 源代码中发生的任何修改。

【讨论】:

vector.back() 返回 T,而不是迭代器。 更新为正确讨论 back()、end() 和“早期”函数体的替代方案。 在回调之前,向量不为空。 emplace_back 调用插入了一个元素。 关于迭代器,如果赋值不兼容,我预计会出现类型检查错误。我也试过自动,它失败了。问题如下所述。不过感谢您的回答。 @Tim v.emplace_back() 调用不可能添加元素。在当前最后一个元素之后不提供“要添加的内容”是 undefined,即使在语法上允许。将至少一个元素添加到空向量的意图等效于使用 v.push_back(std::string s) 和/或 v.insert(v.end(), std::string s)。最大的区别是 emplace_back(Args&&... args) 是可变参数。因此,存在混淆 C++11 允许可变参数列表为零参数的可能性,以及 emplace_back 如果未传递任何内容则具有未定义行为的现实。【参考方案3】:

我自己遇到了这个断言失败。我发现原因是我在循环中调用的东西正在修改我正在迭代的向量。

【讨论】:

以上是关于C++ for-each 语句触发“向量迭代器不兼容”断言失败:this->_Getcont() == 0的主要内容,如果未能解决你的问题,请参考以下文章

关于迭代器的for-each遍历集合现象。。。。。

从多个for-each XSLT段输出HTML

JAVA中的for-each循环与迭代

C++的入门——判断语句

Effective Java 读书笔记之七 通用程序设计

for-each 循环如何工作? [复制]