簡體   English   中英

迭代時從標准集中刪除元素會導致無限循環

[英]Deleting elements from std set while iterating leads to endless loop

以下代碼產生以下輸出,並以 100% cpu 負載的無限循環結束。

#include <iostream>
#include <set>

class Foo{};

void delete_object_from_set(std::set<Foo *>& my_set, Foo* ob)
{
    std::set< Foo *>::iterator setIt;

    std::cout << "Try to delete object  '" << ob << "'..." << std::endl;

    for(setIt = my_set.begin(); setIt != my_set.end(); ++setIt)
    {
        Foo * tmp_ob = *setIt;
        std::cout << "Check object '" << tmp_ob << "'..." << std::endl;
        // compare the objects
        //
        if(ob == tmp_ob)
        {
            // if the objects are equal, delete this object from the set
            //
            std::cout << "Delete object '" << tmp_ob << " from set..." << std::endl;
            setIt = my_set.erase(setIt);
            std::cout << "Deleted object '" << tmp_ob << " from set..." << std::endl;
        }
    }
    std::cout << "loop finished."  << std::endl;    
};

int main()
{
  Foo* ob = new Foo();
  std::set< Foo * > my_set;
  my_set.insert(ob);

  delete_object_from_set(my_set, ob);

  std::cout << "finished" << std::endl;
  return 0;
}

輸出:

Try to delete object  '0x563811ffce70'...
Check object '0x563811ffce70'...
Delete object '0x563811ffce70 from set...
Deleted object '0x563811ffce70 from set...

所以它沒有完成,有 100% 的 CPU 負載。

我知道如何正確地做到這一點(見下文),但我無法理解這里發生了什么。 這不是一個無限循環,因為它應該不斷地輸出一些東西,但它只是不斷地做一些事情 知道什么嗎?

如何以正確的方式做到這一點: 在迭代時從 std::set 中刪除元素以及如何在迭代時從 std::set 中刪除元素

Hokay,所以您問如何在不連續觸發“檢查對象”打印的情況下無限循環。

快速答案(你已經從其他人那里得到了)是在my_set.end()上調用operator++是 UB,因此可以做任何事情。

特別深入研究 GCC(因為 @appleapple 可以在 GCC 上重現,而我在 MSVC 中的測試沒有發現無限循環)揭示了一些有趣的東西:

operator++調用實現為對_M_node = _Rb_tree_increment(_M_node);的調用_M_node = _Rb_tree_increment(_M_node); 那個看起來如下:

static _Rb_tree_node_base*
local_Rb_tree_increment(_Rb_tree_node_base* __x) throw ()
{
    if (__x->_M_right != 0)
        {
            __x = __x->_M_right;
            while (__x->_M_left != 0)
                __x = __x->_M_left;
        }
    else
        {
            _Rb_tree_node_base* __y = __x->_M_parent;
            while (__x == __y->_M_right)
                {
                    __x = __y;
                    __y = __y->_M_parent;
                }
            if (__x->_M_right != __y)
                __x = __y;
        }
    return __x;
}

因此,它默認通過第一個向右找到“下一個”節點,然后一直向左運行。 但! 在調試器中my_set.end()節點顯示以下內容:

(gdb) s
366             _M_node = _Rb_tree_increment(_M_node);
(gdb) p _M_node
$1 = (std::_Rb_tree_const_iterator<Foo*>::_Base_ptr) 0x7fffffffe2b8
(gdb) p _M_node->_M_right
$2 = (std::_Rb_tree_node_base::_Base_ptr) 0x7fffffffe2b8
(gdb) p _M_node->_M_left
$3 = (std::_Rb_tree_node_base::_Base_ptr) 0x7fffffffe2b8

end()節點的左側和右側顯然都指向自身。 為什么? 詢問實施者,但可能是因為它使其他事情更容易或更可優化。 但這確實意味着,在您的情況下,您遇到的 UB 本質上是一個無限循環:

__x->_M_left = __x;

while (__x->_M_left != 0)
  __x = __x->_M_left;  // __x = __x;

再次,這是 GCC 的情況,在 MSVC 上它沒有循環(調試拋出異常,發布只是忽略它;完成循環並打印“循環完成。”和“完成”,好像沒有發生任何奇怪的事情)。 但這就是關於 UB 的“有趣”部分——任何事情都有可能發生......

代碼相當於:

{
   setIt = my_set.begin(); 
   while(setIt != my_set.end())
   {
        Foo * tmp_ob = *setIt;
        std::cout << "Check object '" << tmp_ob << "'..." << std::endl;

        if(ob == tmp_ob)
        {

            std::cout << "Delete object '" << tmp_ob << " from set..." << std::endl;
            setIt = my_set.erase(setIt);
            std::cout << "Deleted object '" << tmp_ob << " from set..." << std::endl;
        }

      ++setIt;
   }
}

調用my_set.erase(setIt); 如果容器的最后一個元素被刪除,則可能返回end() 因此會發生增量,這就是 UB。 它是否會觸發異常取決於實現,但在任何以下點setIt永遠不會等於my_set.end() ,因此無限循環是可能的。

for(setIt = my_set.begin(); setIt != my_set.end();)
{
    Foo * tmp_ob = *setIt;
    std::cout << "Check object '" << tmp_ob << "'..." << std::endl;
    // compare the objects
    //
    if(ob == tmp_ob)
    {
        // if the objects are equal, delete this object from the set
        //
        std::cout << "Delete object '" << tmp_ob << " from set..." << std::endl;
        setIt = my_set.erase(setIt);
        std::cout << "Deleted object '" << tmp_ob << " from set..." << std::endl;
    }
    else
        setIt++;

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM