std::memcpy 将 TriviallyCopyable 类型 T 的成员紧密压缩到 T 数组的结构,反之亦然

std::memcpy struct with tightly packed members of TriviallyCopyable type T to an array of T and vise versa

已经确定将类型 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]);
}

合法吗?

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

https://en.wikipedia.org/wiki/Data_structure_alignment

您的代码可能 运行 在 C++ 编译器上运行良好,但在另一个编译器甚至是具有不同配置的同一编译器上失败。

另请注意,您使用的数组指针不正确。

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

而不是

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

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

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

给定一个普通可复制类型 T,明确允许将其表示复制到 char(或 unsigned char)数组中并返回。

不要求数组的内容保留在数组本身中。内容可以存储在一个文件中,并且 re-read 在程序的后续执行中。或者存储在不同类型的对象中,只要该类型允许。为此,实现必须允许 memcpying 对象的表示,当这些表示并非源自同一 运行.

类型的对象时 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]);
}

要么在第一个 assert 上失败,要么通过。对于任何会失败的表示,创建一个函数恰好以明确有效的方式提出该精确表示是微不足道的,因此它一定不会失败。

现在,在这里省略 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 通过 tmp 所做的。

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

就我个人而言,我可能不会担心这一点,除非我确实遇到了使用该解释的实现,但如果您想安全使用,您可以引入这样一个临时数组,并验证您的编译器是否设法优化它。