使用 Visual C++ 提升内存访问失败

问题描述 投票:0回答:1

我正在考虑在使用 Visual C++ 编译的项目中使用

boost te
库(MSVC 19.29.30153.0 与 c++ 标准 20)。我举个最简单的例子擦除它,本质上是:

struct Drawable {
  void draw(std::ostream &out) const {
    te::call([](auto const &self, auto &out) { self.draw(out); }, *this, out);
  }
};

struct Square {
  void draw(std::ostream &out) const { out << "Square"; }
};

int main() {
  te::poly<Drawable> d = Square{};
  d.draw();
}

这可以编译,但在运行时失败,此时在

te::poly
:

  return reinterpret_cast<R (*)(void *, Ts...)>(self.vptr[N - 1])(
      self.ptr(), std::forward<Ts>(args)...);

发生读访问冲突:

抛出异常:读取访问冲突。 self 是 0xFFFFFFFFFFFFFFFF。

如果我添加一行来观察变量:

auto my_pointer = self.ptr();

它确实无法调用它,并且在检查变量时:

self    {vptr=0x00007ff6698614e2 {check_te_windows.exe! ...
+       __vfptr 0x00000142775a64a0 {0x000000fdfdfdfdcd} void * *
+       vptr    0x00007ff6698614e2 

vtable 指针似乎确实损坏了。这个库已经在 clang、gcc 和 intels oneapi 编译器中为我工作了。这是一个 gcc 实例:https://godbolt.org/z/boKaY19oj

有人知道 Visual C++ 编译器可能出现什么问题吗?

c++ visual-c++ boost c++17 type-erasure
1个回答
0
投票

在类模板中

poly
构造函数初始值设定项列表显示
vtable
的初始值设定项在
storage
之前,但
storage
仍然首先初始化,因为声明顺序:

  constexpr explicit poly(T &&t, std::index_sequence<Ns...>) noexcept(std::is_nothrow_constructible_v<T_,T&&>)
      : detail::poly_base{},
        vtable{std::forward<T>(t), vptr,
               std::integral_constant<std::size_t, sizeof...(Ns)>{}},
        storage{std::forward<T>(t)} {
    static_assert(sizeof...(Ns) > 0);
    static_assert(std::is_destructible_v<T_>, "type must be desctructible");
    static_assert(std::is_copy_constructible_v<T_> ||
                  std::is_move_constructible_v<T_>,
                  "type must be either copyable or moveable");
    (init<Ns + 1, std::decay_t<T> >(
         decltype(get(detail::mappings<I, Ns + 1>{})){}),
     ...);
  }

这是可疑的,但更可疑的是双重

std::forward
,它可能会导致
t
论证的双重移动。在您的示例中,确实使用以下模板参数调用了这个可疑的构造函数:

constexpr boost::ext::te::v1::poly<I, TStorage, TVtable>::poly(T&&, std::index_sequence<Ns ...>);
// with T = Square;
// T_ = Square
// long unsigned int ...Ns = {0}
// I = Drawable
// TStorage = boost::ext::te::v1::dynamic_storage
// TVtable = boost::ext::te::v1::static_vtable
// std::index_sequence<Ns ...> = std::integer_sequence<long unsigned int, 0>

检测 Square 类以禁止移动,使其无法编译:https://godbolt.org/z/v4bjYf18b

想法1

基于这些观察,您可能会考虑避免右值参数(您的临时参数):https://godbolt.org/z/cMhd6Pa4h(当然,在 GCC 上没有问题,但检查 MSVC?)

int main() {
    Circle c;
    Square s;
    draw(c); // prints Circle
    draw(s); // prints Square
}

想法2

您可以翻转

poly
成员的声明顺序:

TStorage storage;
TVtable vtable;

至:

TVtable vtable;
TStorage storage;

当然,这会改变布局,并且根据邪恶的做法可能会导致其他事物改变行为。

总结、提示

无论如何,我建议向 TE 开发人员提出问题。

还可以考虑使用 Boost Type Erasure,它是 Boost 的正确组成部分:https://www.boost.org/doc/libs/1_84_0/doc/html/boost_typeerasure.html

© www.soinside.com 2019 - 2024. All rights reserved.