通过从unique_ptrs列表中弹出前面会使clang静态分析器感到困惑吗?

 七彩咩_131 发布于 2023-01-17 11:38

以下C++ 11代码是我认为在clang中触发误报的最小示例:

#include 
#include 
#include 

class ElementType {};

int main(int argc, const char * argv[]) {
    std::list> theList(5);

    theList.pop_front();

    for (const auto &element: theList) { // (*)
        std::cout << "This should be fine." << std::endl;
    }

    return 0;
}

在由星号(*)标记的线上,铿锵声分析仪声称

... filePath ... /main.cpp:21:29:释放后使用内存(在调用'begin'时)

据我解释,这段代码是无害的,但是clang忽略了这样的观点:std::list::pop_front()不仅要调用它的元素的析构函数,还要调整它的位置std::list::begin().更换呼叫pop_front通过pop_back使得该分析仪警告消失,甚至被替换它erase(theList.begin())使它走出无警告.

我是否遗漏了某些东西或者我是否真的偶然发现了一个错过的铿锵声?

供参考:这些结果来自Mac OS X 10.9.2上的XCode 5.1.1(5B1008),

$ clang --version
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.1.0
Thread model: posix

Matthieu M... 5

现在的代码看起来很好.

我检查了libc ++(相关部分)的代码,我相信它只会混淆静态分析器.

更多细节:

template 
void list<_Tp, _Alloc>::pop_front()
{
    _LIBCPP_ASSERT(!empty(), "list::pop_front() called with empty list");
    __node_allocator& __na = base::__node_alloc();
    __node_pointer __n = base::__end_.__next_;
    base::__unlink_nodes(__n, __n);
    --base::__sz();
    __node_alloc_traits::destroy(__na, _VSTD::addressof(__n->__value_));
    __node_alloc_traits::deallocate(__na, __n, 1);
}

list实现为循环列表,基于__end_(它是结束指针),所以为了到达第一个元素,代码转到__end_.__next_.

执行__unlink_nodes是:

// Unlink nodes [__f, __l]
template 
inline void __list_imp<_Tp, _Alloc>::__unlink_nodes(__node_pointer __f,
                                                    __node_pointer __l) noexcept
{
    __f->__prev_->__next_ = __l->__next_;
    __l->__next_->__prev_ = __f->__prev_;
}

我们可以通过一些简单的ASCII艺术轻松理解它:

       Z             A             B             C
  +---------+   +---------+   +---------+   +---------+
--| __prev_ |<--| __prev_ |<--| __prev_ |<--| __prev_ |<-
->| __next_ |-->| __next_ |-->| __next_ |-->| __next_ |--
  +---------+   +---------+   +---------+   +---------+

要删除范围A- B从此列表中:

Z.__next_ 不得不指出 C

C.__prev_ 不得不指出 Z

因此,电话__unlink_nodes(A, B)会:

A.__prev_.__next_(即Z.__next_)并使其指向B.__next_(即C)

B.__next_.__prev_(即C.__prev_)并使其指向A.__prev_(即Z)

这很简单,即使在使用单个元素范围(此处为大小写)调用时也可以使用.

但是,现在请注意,如果它list是空的,这根本不起作用!默认构造函数__list_node_base是:

__list_node_base()
    : __prev_(static_cast(pointer_traits<__base_pointer>::pointer_to(*this))),
      __next_(static_cast(pointer_traits<__base_pointer>::pointer_to(*this)))
      {}

也就是说,它指的是它自己.在这种情况下,__unlink_nodes&__end_(两次)调用,并且不会改变它__end_.__prev_.__next_ = __end_.__next_是幂等的(因为__end_.prev__end_本身).

可能是这样的:

分析器考虑空列表的情况(_LIBCPP_ASSERT编译出来)

并得出结论,在这种情况下,__end_.__next_(begin()使用者)被deallocate()呼叫留下悬空pop_front()

或者也许它是指针舞中的其他东西......希望Clang团队能够修补它.

1 个回答
  • 现在的代码看起来很好.

    我检查了libc ++(相关部分)的代码,我相信它只会混淆静态分析器.

    更多细节:

    template <class _Tp, class _Alloc>
    void list<_Tp, _Alloc>::pop_front()
    {
        _LIBCPP_ASSERT(!empty(), "list::pop_front() called with empty list");
        __node_allocator& __na = base::__node_alloc();
        __node_pointer __n = base::__end_.__next_;
        base::__unlink_nodes(__n, __n);
        --base::__sz();
        __node_alloc_traits::destroy(__na, _VSTD::addressof(__n->__value_));
        __node_alloc_traits::deallocate(__na, __n, 1);
    }
    

    list实现为循环列表,基于__end_(它是结束指针),所以为了到达第一个元素,代码转到__end_.__next_.

    执行__unlink_nodes是:

    // Unlink nodes [__f, __l]
    template <class _Tp, class _Alloc>
    inline void __list_imp<_Tp, _Alloc>::__unlink_nodes(__node_pointer __f,
                                                        __node_pointer __l) noexcept
    {
        __f->__prev_->__next_ = __l->__next_;
        __l->__next_->__prev_ = __f->__prev_;
    }
    

    我们可以通过一些简单的ASCII艺术轻松理解它:

           Z             A             B             C
      +---------+   +---------+   +---------+   +---------+
    --| __prev_ |<--| __prev_ |<--| __prev_ |<--| __prev_ |<-
    ->| __next_ |-->| __next_ |-->| __next_ |-->| __next_ |--
      +---------+   +---------+   +---------+   +---------+
    

    要删除范围A- B从此列表中:

    Z.__next_ 不得不指出 C

    C.__prev_ 不得不指出 Z

    因此,电话__unlink_nodes(A, B)会:

    A.__prev_.__next_(即Z.__next_)并使其指向B.__next_(即C)

    B.__next_.__prev_(即C.__prev_)并使其指向A.__prev_(即Z)

    这很简单,即使在使用单个元素范围(此处为大小写)调用时也可以使用.

    但是,现在请注意,如果它list是空的,这根本不起作用!默认构造函数__list_node_base是:

    __list_node_base()
        : __prev_(static_cast<pointer>(pointer_traits<__base_pointer>::pointer_to(*this))),
          __next_(static_cast<pointer>(pointer_traits<__base_pointer>::pointer_to(*this)))
          {}
    

    也就是说,它指的是它自己.在这种情况下,__unlink_nodes&__end_(两次)调用,并且不会改变它__end_.__prev_.__next_ = __end_.__next_是幂等的(因为__end_.prev__end_本身).

    可能是这样的:

    分析器考虑空列表的情况(_LIBCPP_ASSERT编译出来)

    并得出结论,在这种情况下,__end_.__next_(begin()使用者)被deallocate()呼叫留下悬空pop_front()

    或者也许它是指针舞中的其他东西......希望Clang团队能够修补它.

    2023-01-17 11:43 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有