将Vec*类型强制转换为double*类型

Casting a Vec* to a double*

本文关键字:类型 double 转换 Vec      更新时间:2023-10-16

我正试图理解一个框架,我已经提供并慢慢调试它。在我的例子中,我有一个Vec数组,用指针记忆:

Vec *cx =....;

My Vec是一个结构体,定义如下:

struct Vec {
    double x, y, z;                  // position, also color (r,g,b)
    Vec(double x_ = 0, double y_ = 0, double z_ = 0){ x = x_; y = y_; z = z_; }
    Vec operator+(const Vec &b) const { return Vec(x + b.x, y + b.y, z + b.z); }
    Vec operator-(const Vec &b) const { return Vec(x - b.x, y - b.y, z - b.z); }
    Vec operator-() const { return Vec(-x, -y, -z); }
    Vec operator*(double b) const { return Vec(x*b, y*b, z*b); }
    Vec mult(const Vec &b) const { return Vec(x*b.x, y*b.y, z*b.z); }
    Vec& norm(){ return *this = *this * (1 / sqrtf(x*x + y*y + z*z)); }
    double dot(const Vec &b) const { return x*b.x + y*b.y + z*b.z; } // cross:
    Vec operator%(Vec&b){ return Vec(y*b.z - z*b.y, z*b.x - x*b.z, x*b.y - y*b.x); }
    double max() const { return x>y && x>z ? x : y > z ? y : z; }
};

我想知道如果我将Vec数组强制转换为double数组会发生什么。在实践中:

double * cast = (double*)cx;

它给了我什么?

为了理解,我试着这样做:

Vec boh = Vec(1.0, 1.0, 1.0);
Vec boh2 = Vec(2.0, 2.0, 2.0);
Vec * try = new Vec[2];
try[0] = boh;
try[1] = boh2;
double* try2= (double*)try;

我意识到try指向第一个Vec,它是boh1,所以如果我去调试,我看到x,y,z值设置为1。同样,try2应该指向与boh1相关的"某些东西",但调试显示try2的值为1.000000。那么,到底发生了什么呢?

编辑:"try"不能用作c++中的变量名。

它仍然会给你一个指向Vec的指针,这根本不是double。强制转换所做的是告诉编译器完全忽略try的实际类型,有效地假装try是它不是的东西。

使用try2将导致未定义行为

正如其他人指出的那样,你所做的是非常错误的——我不再重复细节了。


为什么在调试器中看到1.0000 ?原因如下:

try是一个指针,因此它在Vec * try = new Vec[2];之后的值是分配给数组的某个内存地址。当你执行double* try2 = (double*)try;时,你告诉编译器取try的值并将其复制到try2。就是这个值,它是一个被复制的内存地址。

之后,如果您观察*try2中存在的值,您将看到try2所指向的地址上double值的文本表示。由于您将Vec结构体复制到该地址,因此您将看到的值来自该数据,该数据在相对内存位置0处具有double -这是Vecx成员,您将1.0赋值给它。

同样,所有这些都可以而且应该被认为是巧合,您不应该一开始就进行这样的强制转换。

什么是"实际"发生的是,因为你的结构体的数据成员是3双,你的同事是指望C的行为,他们正在看的内存作为double而不是Vec。在C中,它的行为就像一个"联合",也就是说,在内存中布局的结构体只是3个双精度,一个接一个。在您的情况下(使用c++),它恰好(参见EDIT)表现得像一个联合。因此,您从boh(您称之为boh1)访问1.0。其他人说,这种行为是不明确的。我相信"依赖于实现"可能更准确,但我会遵从他们(除非我能找到参考)。

无论如何,如果可以的话,您的代码示例已经可以重构了。如果你的同事依赖于这种行为,他们应该清楚地认识到这一点,这样其他人就不会紧随其后,改变结构。

EDIT:好吧,我找到了一个参考,表明这是不是未定义的行为(除非我误解了参考)。该结构体是c++规范第9节(ss7,10)中的POD结构体。POD(普通旧数据)布局在9.2节(ss13)中有描述。

9 ss10
POD结构体110是一个非联合类,它既是一个普通类,也是一个标准布局类,并且没有非POD结构体、非POD联合类型(或此类类型的数组)的非静态数据成员。类似地,POD联合是一个普通类和标准布局类的联合,并且没有非POD结构、非POD联合(或此类类型的数组)类型的非静态数据成员。POD类是一个类,它要么是POD结构体,要么是POD联合。

9 ss7
一个类S是一个标准布局类,如果它:
-(7.1)没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员;
-(7.2)没有虚函数(10.3)和虚基类(10.1),
-(7.3)对所有非静态数据成员具有相同的访问控制(条款11),
-(7.4)没有非标准布局的基类,
-(7.5)最多有一个给定类型的基类子对象,
-(7.6)在类及其基类中首先声明所有非静态数据成员和位字段,并且
-(7.7)没有类型集合M(S)(定义如下)中的元素作为基类。109

9.2 ss13
具有相同访问控制(第11条)的(非联合)类的非静态数据成员被分配,以便稍后的成员在类对象中拥有更高的地址。具有不同访问控制的非静态数据成员的分配顺序未指定(第11条)。实现一致性要求可能导致两个相邻的成员不能在彼此之后立即分配;管理虚函数(10.3)和虚基类(10.1)的空间需求也是如此。

因此,我怀疑你的同事是依靠POD布局来获得类似"union"的行为。我仍然认为这是一种非常糟糕的做法。如果无法避免,则应该添加编译时检查,或者应该记录结构以识别此需求。

程序的行为未定义。

本质上,sizeof(double)sizeof(Vec)不一样,所以所有的指针算术和数组索引都会在c风格的强制转换后失效。