c++ - 在两个相似类之间使用 reinterpret_cast 时出现错误?

标签 c++ undefined-behavior reinterpret-cast

我有一个用作矩阵/vector 类的数组包装器,以及一个由两个 float 组成的结构体来表示点。
我不想再次为点重新定义所有算术运算符,因为我已经有了它们可用于 vector ,所以我想在它们之间添加隐式转换。我用 reinterpret_cast ,如下面的代码片段所示。

template <class T, size_t N>
struct Array {
    T data[N];
    constexpr T &operator[](size_t index) { return data[index]; }
    constexpr const T &operator[](size_t index) const { return data[index]; }
};

template <class T, size_t R, size_t C>
using TMatrix = Array<Array<T, C>, R>;

template <class T, size_t R>
using TColVector = TMatrix<T, R, 1>;

struct Point {
    float x;
    float y;

    constexpr Point(float x, float y) : x{x}, y{y} {}
    constexpr Point(const TColVector<float, 2> &vec) : x{vec[0]}, y{vec[1]} {}

    TColVector<float, 2> &vec() {
        static_assert(sizeof(*this) == sizeof(TColVector<float, 2>));
        return *reinterpret_cast<TColVector<float, 2> *>(this);
    }
    operator TColVector<float, 2> &() { return vec(); }
};

使用 Point 的隐式转换时至 TColVector<float, 2> ,我得到不正确的结果。更奇怪的是:只要我打印中间结果,结果就是正确的,但是当我注释掉打印语句时,结果就不正确。它似乎在 x86 的 gcc 7.3.0 上总是正确的,有时在 ARMv7 的 gcc 8.3.0 上不正确。

这个函数在打印语句时给出了正确的结果,而在我注释掉打印语句时给出了错误的结果:

static float distanceSquared(Point a, Point b) {
    using namespace std;
    // cout << "a = " << a << ", b = " << b << endl;
    auto diff = a.vec() - b.vec(); // Array<T, N> operator-(const Array<T, N> &lhs, const Array<T, N> &rhs)
    // cout << "diff = " << Point(diff) << endl;
    auto result = normsq(diff); // auto normsq(const TColVector<T, C> &colvector) -> decltype(colvector[0] * colvector[0])
    // cout << "normsq(diff) = " << result << endl;
    return result;
}

我是不是做错了什么?

解决方案似乎是这样的(即使它不能用作左值):

TColVector<float, 2> vec() const { return {x, y}; }

我试图将问题与我的项目的其余部分隔离开来,但我无法单独重现它,所以我想知道我是否必须继续寻找其他问题,尽管看起来现在好了。

这是 GitHub 上的完整代码(它似乎并没有单独说明问题):https://github.com/tttapa/random/blob/master/SO-reinterpret_cast.cpp

最佳答案

您的代码有未定义的行为。你不能只是 reinterpret_cast一个Point*Array<Array<float, 2>, 1>* .仅此转换的结果可能未指定(这里的 reinterpret_cast 调用 [expr.reinterpret.cast]/7 指针转换为 void* [expr.static.cast]/4 [conv.ptr]/2 (仍然可以),然后从 void* 转换为 Array<Array<float, 2>, 1>* ,这如果 Point 的对齐结果至少不如 Array<Array<float, 2>, 1> [expr.static.cast]/13 的对齐那么可能是未指定的)。即使转换本身恰好成功,您也不允许取消对结果指针的引用并访问结果左值所引用的对象。这样做会违反严格的别名规则 [basic.lval]/11 (例如,参见 herehere 了解更多信息)。您的两种类型在实践中可能最终具有相同的内存布局,但它们不是指针可相互转换的 [basic.compound]/4 .打印中间结果很可能会阻止编译器根据那里的未定义行为执行优化,这就是问题没有出现的原因......

恐怕您必须考虑其他一些解决方案,例如,只为 Point 实现必要的运算符。 .或者只返回 Array<Array<float, 2>, 1>从你的 x 初始化和 y .如果只用在表达式中,即 Array<Array<float, 2>, 1>通常应该最终被优化掉(因为相关部分都是这里的模板,它们的定义是已知的并且编译器应该内联所有这些东西)。或者让你的 Point 列 vector

struct Point : TColVector<float, 2> {};

并定义一些存取函数来得到x(point)y(point) ……

关于c++ - 在两个相似类之间使用 reinterpret_cast 时出现错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56094618/

相关文章:

c++ - 增加初始化列表中使用两次的变量 - 未定义的行为?

c++ - C++03 和 C++11 之间的未定义行为有什么区别?

c++ - 在 C++11 中, `i +=++i + 1` 是否表现出未定义的行为?

c++ - 将指针强制转换为整数、递增该整数,然后强制转换回来是否安全?

C++ 错误 Expected primary expression before "' "token (using getline on an ifstream?)

c++ - GRPC:客户端超时

c++ - 根据用户输入计算数组中的可整除数

c++ - 在单独的线程中插入时读取 std::map

c++ - reinterpret_cast 和 null 成员变量

c++ - std::array 位是否与旧的 C 数组兼容?