C++ std::memcpy struct,具有可复制类型T的紧密压缩成员到T数组,反之亦然

C++ std::memcpy struct,具有可复制类型T的紧密压缩成员到T数组,反之亦然,c++,type-conversion,language-lawyer,type-punning,structure-packing,C++,Type Conversion,Language Lawyer,Type Punning,Structure Packing,已确定将T类型的紧密压缩连续结构成员视为T的数组是非法的 但是复制底层表示又如何呢 鉴于: struct vec { float x, y, z; }; 使用相同的约束条件: static_assert(sizeof(vec) == 3 * sizeof(float)); 详情如下: int main() { vec v = {1.9f, 2.5f, 3.1f}; float a[3]; std::memcpy(&a, &v, 3 * siz

已确定将T类型的紧密压缩连续结构成员视为T的数组是非法的

但是复制底层表示又如何呢

鉴于:

struct vec {
    float x, y, z;
};
使用相同的约束条件:

static_assert(sizeof(vec) == 3 * sizeof(float));
详情如下:

int main() {
    vec v = {1.9f, 2.5f, 3.1f};

    float a[3];
    std::memcpy(&a, &v, 3 * sizeof(float));
    assert(a[0] == v.x);
    assert(a[1] == v.y);
    assert(a[2] == v.z);

    vec u;
    std::memcpy(&u, &a, 3 * sizeof(float));
    assert(u.x == a[0]);
    assert(u.y == a[1]);
    assert(u.z == a[2]);
}

法律?

为什么不应该总是相信由三个相同类型的成员组成的结构等同于一个相同类型的数组,这主要是因为内存对齐

<>你的代码可以在C++编译器上运行正常,而在另一个甚至不同配置的编译器上失败。 还请注意,您使用的数组指针不正确

std::memcpy(a, &v, 3 * sizeof(float));
而不是

std::memcpy(&a, &v, 3 * sizeof(float));

a已经是指向float的常量指针

只要结构类型没有任何填充,标准中就没有对它的明确支持,但是可以推断出对非常接近它的内容的支持

给定一个普通的可复制类型
T
,显式允许的是将其表示形式复制到
char
(或
无符号char
)数组中并返回

不要求将数组的内容保存在数组本身中。内容可以存储在一个文件中,并在随后执行程序时重新读取。或者存储在不同类型的对象中,只要该类型允许。若要实现这一点,实现必须允许
memcpy
在同一次运行中,当这些表示不是来自
T
类型的对象时,将这些表示嵌入到对象中

因此,至少

int main() {
    vec v = {1.9f, 2.5f, 3.1f};

    float a[3];

    assert(sizeof v == sizeof a);

    { char tmp[3 * sizeof(float)];
      std::memcpy(tmp, &v, 3 * sizeof(float));
      std::memcpy(a, tmp, 3 * sizeof(float)); }
    assert(a[0] == v.x);
    assert(a[1] == v.y);
    assert(a[2] == v.z);

    vec u;
    { char tmp[3 * sizeof(float)];
      std::memcpy(tmp, a, 3 * sizeof(float));
      std::memcpy(&u, tmp, 3 * sizeof(float)); }
    assert(u.x == a[0]);
    assert(u.y == a[1]);
    assert(u.z == a[2]);
}
第一次断言失败或通过。对于任何可能失败的表示,创建一个恰好以明确有效的方式给出精确表示的函数是很简单的,因此它不能失败

现在,在这里省略
tmp
有点不确定

std::memcpy
只是单个字节的重复赋值,可以被显式地拼写出来。
=
操作符的语义意味着对于可复制的类型,
a=b
{auto-tmp=b;a=tmp;}
是等效的。与
a=b相同;c=d
{auto-tmp1=b;auto-tmp2=d;a=tmp1;c=tmp2;}
,依此类推。前者是直接
memcpy
所做的,后者是两个
memcpy
s通过
tmp
所做的

另一方面,在
char
数组中进行复制和复制的权限可以理解为需要实际的
char
数组,而不仅仅是它的功能等价物


就个人而言,我可能不会担心这个问题,除非我真的遇到了一个使用这种解释的实现,但如果您想安全起见,可以引入这样一个临时数组,并验证编译器是否能够对其进行优化。

相关(或可能重复):@Barmar这是我链接的问题中讨论的同一个问题。他们都说这是未定义的行为。为什么你认为你的案子有什么不同?为什么?为什么要冒险?使用
运算符=
。这就是它的目的。@Barmar,因为通过不同类型使用相同内存和将所述内存复制到不同类型的不同位置之间存在差异。不存在对齐问题,因为
float
s始终具有相同的对齐要求,无论它们存储在何处。尽管存在填充问题,但我对使用
static\u assert
的问题有所防范。至于指向数组的指针和指向第一个数组元素的指针,这并不重要,因为它们在转换为指向void的指针时是相同的。@yurikilochek其他类型有不同的对齐要求,这取决于它们是否是结构的一部分(
long
,在x86 GNU/Linux系统上),为什么不能
float
?但是,如果填充检查正常,对齐不应该在这里产生问题。@hvd我的印象是,这适用于所有类型,而不仅仅是浮点数。但你是对的,如果不引入填充,这并不重要。@yurikilochek:据我所知,编译器可以出于任何他们认为合适的原因在每个结构成员后插入任意数量的填充,只要POD的公共初始序列的布局相同。@supercat是的,那么?