微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

可能的 MSVC 编译器错误

如何解决可能的 MSVC 编译器错误

鉴于在 for 循环和 for 循环体的条件子句中声明的 shared_ptr 变量包含 if/continue 语句,Microsoft 编译器(自 2015 版起)每次循环迭代都会生成额外的析构函数调用(总共两个)。这会导致 Holder 界面用户无法访问的 Item 对象被破坏。 请参阅下面的示例代码

namespace
{
    class Item
    {
    public:
        Item(size_t v)
            : value_(v)
        {
            std::cout << "Item(" << value_ << ")" << std::endl;
        }

        ~Item()
        {
            std::cout << "~Item(" << value_ << ")" << std::endl;
        }

        void print() const
        {
            std::cout << "Item::print(" << value_ << ")" << std::endl;
        }

    private:
        size_t value_;

    };

    typedef std::shared_ptr<const Item> ItemCPtr;

    class Holder
    {
    public:
        Holder(size_t n)
        {
            for (size_t i = 0; i < n; ++i)
                items_.emplace_back(new Item(i));
        }

        ItemCPtr getItem(size_t i) const
        {
            if (i < items_.size())
                return items_[i];
            return ItemCPtr();
        }

    private:
        std::vector<ItemCPtr> items_;
    };
}

TEST(Test,Test)
{
    Holder _holder(5);

    std::cout << "before loop" << std::endl;

    for (size_t i = 0; auto _item = _holder.getItem(i); ++i)
    {
        if (!!(i % 2))
            continue;

        _item->print();
    }

    std::cout << "after loop" << std::endl;

    _holder.getItem(1)->print();
    _holder.getItem(3)->print();
}

下面这个产量输出

||   [ RUN      ] Test.Test
||   Item(0)
||   Item(1)
||   Item(2)
||   Item(3)
||   Item(4)
||   before loop
||   Item::print(0)
||   ~Item(1)
||   Item::print(2)
||   ~Item(3)
||   Item::print(4)
||   after loop
||   Item::print(3722304989)
||   Item::print(3722304989)
||   ~Item(0)
||   ~Item(2)
||   ~Item(4)
||   [       OK ] Test.Test (0 ms)

如果我以这种方式将 _item 声明移出 for 循环

    auto _item = ItemCPtr();
    for (size_t i = 0; _item = _holder.getItem(i); ++i)

然后我得到这样的预期输出

||   [ RUN      ] Test.Test
||   Item(0)
||   Item(1)
||   Item(2)
||   Item(3)
||   Item(4)
||   before loop
||   Item::print(0)
||   Item::print(2)
||   Item::print(4)
||   after loop
||   Item::print(1)
||   Item::print(3)
||   ~Item(0)
||   ~Item(1)
||   ~Item(2)
||   ~Item(3)
||   ~Item(4)
||   [       OK ] Test.Test (0 ms)

据我所知,getItem 应该生成 ItemCPtr 的副本,并且不能通过 Holder 接口修改任何项目。然而,用户可以在循环内销毁五分之二的项目,请参阅 ~Item(1)/~Item(3) 标记间的 before loopafter loop 析构函数输出

这是暴露问题的微不足道的例子。在现实世界中,这会导致难以跟踪的内存损坏问题。

编译器标识:

cmake -G "Visual Studio 14 2015" ..\
-- Selecting Windows SDK version 10.0.14393.0 to target Windows 10.0.19041.
-- The C compiler identification is MSVC 19.0.24210.0
-- The CXX compiler identification is MSVC 19.0.24210.0

操作系统是 64 位 Windows 10

即使禁用了优化/Od认选项也会出现错误

解决方法

据我所知,getItem 应该生成 ItemCPtr 的副本,并且不能通过 Holder 接口修改任何项。

您的理解是 100% 准确的。这正是为 C++ 标准的抽象机器描述的行为。

即使在允许编译器优化的 as-if 规则下,可观察到的行为(由于打印到标准输出流)应该好像至少有一个共享指针(对于每个项目)存在直到测试范围的结束。不过,这显然不是您所看到的。

我有根据的猜测是 MSVC 完全优化了副本。而是直接在循环体中引用向量内部的指针。这本身没问题。

错误可能是它错误处理了在 continue 语句的情况下会破坏局部变量的代码。并且错误地将其应用于向量中的对象。这是一个错误。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。