0

Here is has been established that it is illegal to treat tightly packed consecutive struct members of type T as an array of T.

But what about copying the underlying representation?

Given:

struct vec {
    float x, y, z;
};

with the the same constraints:

static_assert(sizeof(vec) == 3 * sizeof(float));

is the following:

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]);
}

legal?

Community
  • 1
  • 1
yuri kilochek
  • 12,709
  • 2
  • 32
  • 59

2 Answers2

1

As long as your structure type doesn't have any padding, there's no explicit support for it in the standard, but support for something very close to it can be inferred.

Given a trivially copyable type T, what's explicitly allowed is to copy its representation into an array of char (or unsigned char) and back.

There is no requirement that the contents of the array are preserved in the array itself. The contents could be stored in a file, and re-read on a subsequent execution of the program. Or stored in an object of a different type, so long as that type allows it. For this to work, implementations must allow memcpying representations into objects when those representations did not originate from objects of type T in that same run.

As a result, at the very least,

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]);
}

should either fail on the first assert, or pass. For any representation where it'd fail, it's trivial to create a function which happens to come up with that exact representation in unambiguously valid ways, therefore it mustn't fail.

Now, omitting tmp here is a bit iffy.

std::memcpy is just repeated assignments of the individual bytes and could have been spelled out explicitly. The semantics of the = operator imply that for trivially copyable types, a = b; and { auto tmp = b; a = tmp; } are equivalent. Same with a = b; c = d; and { auto tmp1 = b; auto tmp2 = d; a = tmp1; c = tmp2; }, and so on. The former is what a direct memcpy does, the latter is what two memcpys through tmp do.

On the other hand, the permission to copy in and out of an array of char could be read as requiring an actual array of char, not merely the functional equivalent of it.

Personally, I probably would not worry about that unless I actually came across an implementation which uses that interpretation, but if you want to play it safe, you can introduce such a temporary array, and verify that your compiler manages to optimise it away.

-3

The concern why you shouldn't always trust that a struct of three members of the same type is equivalent to an array of the same type is basically because of memory alignment.

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

Your code could run alright on a C++ compiler and fail on another or even on the same compiler with different configuration.

Also notice that you are using the array pointer incorrectly.

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

and not

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

a is already a constant pointer to float

Bishoy
  • 705
  • 9
  • 24
  • 1
    There are no alignment issues, as `float`s always have the same alignment requirements regardless of where they are stored. There _are_ padding concerns though, but I guard against those with the `static_assert`. As for pointer-to-array vs pointer-to-first-array-element thing, it does not matter as they are the same when converted to pointer-to-void. – yuri kilochek Feb 04 '17 at 09:24
  • @yurikilochek Other types have a different alignment requirement depending on whether they are part of a struct (`long long` on x86 GNU/Linux systems), why can't `float`? But alignment shouldn't cause a problem here if your padding check is working. –  Feb 04 '17 at 09:49
  • @hvd I was under impression that was true for all types, not just floats. But you are correct, it does not matter if no padding is introduced. – yuri kilochek Feb 04 '17 at 10:40
  • @yurikilochek: From what I understand, compilers are allowed to insert arbitrary amounts of padding after each structure member for any reason they see fit, provided that common initial sequences of PODS are laid out identically. – supercat Feb 13 '17 at 17:17
  • @supercat yes, so? – yuri kilochek Feb 13 '17 at 19:04
  • @yurikilochek: If a compiler places each structure member at the first available offset that would fit its alignment, as is typical *but not required*, then consecutive structure members would be placed the same as consecutive array elements. If a compiler opted to add extra padding, which would be atypical *but not forbidden*, then trying to use `memcpy` to copy all of the members to/from an array would fail. – supercat Feb 13 '17 at 19:44
  • @supercat, yes, I am aware. That is why `static_assert` is there. – yuri kilochek Feb 14 '17 at 05:51