5

Given

template <typename T>
struct Vector3d { T x, y, z; };
  • Is it safe to assume that x, y, and z are in contiguous memory locations?

  • Is it at least safe to assume that for T = float and T = double?

  • If not is it possible to enforce in a cross-platform way?

Note: I don't mind padding after z as long as x, y, z are contigious

AMA
  • 4,114
  • 18
  • 32
  • 3
    No I don't think so, the compiler can add padding between the members (and after the last member). For `T` being a `double` it's *unlikely* that there will be padding, but it's still not guaranteed. – Some programmer dude Sep 05 '19 at 15:30
  • Padding between two variables may exist inside structs. I guess it wouldn't be a problem if for example you had multiple `int`. – DimChtz Sep 05 '19 at 15:30
  • Why not to use array if need vars of the same type? Array elements would be in continuous memory – Slava Sep 05 '19 at 15:31
  • @Slava because I would like to have just `v.x` syntax for reading and modification instead of `v.buf[0]` – AMA Sep 05 '19 at 15:33
  • If you want contiguous locations, you need to inform the compiler that the `struct` is packed on byte boundary (using a `#pragma pack(1)` or similar -- please consult your compiler docs). – PaulMcKenzie Sep 05 '19 at 15:33
  • 2
    Pragmatic solution: `static_assert(sizeof(Vector3d) == 3 * sizeof(T));`. It is very unlikely for any compiler to violate this, and so this should work fine in practice while still protecting you against subtly wrong behavior on some pathological platform. – Max Langhof Sep 05 '19 at 15:34
  • [Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other...](http://eel.is/c++draft/class.mem#19.sentence-3). The question is — if `alignof(T)` equals `sizeof(T)`, does it imply anything here? – Daniel Langr Sep 05 '19 at 15:34
  • 1
    You can use `v.x()` instead – Slava Sep 05 '19 at 15:35
  • @DanielLangr how array of `float`s would work in this case? – Slava Sep 05 '19 at 15:38
  • 1
    For what usage do you want to use this information? Using something like `&x[2]` for accessing `z` is UB, even if there is no padding. – geza Sep 05 '19 at 15:39
  • @Slava You're right, my bad, padding is part of an object size. Will delete. – Daniel Langr Sep 05 '19 at 15:39
  • @geza could you please elaborate why? – AMA Sep 05 '19 at 15:42
  • 3
    @AMA: http://eel.is/c++draft/expr.add#4.3 – geza Sep 05 '19 at 15:44
  • @geza The question might be motivated by possible utilization of SIMD instructions: https://godbolt.org/z/Nm4J59. – Daniel Langr Sep 05 '19 at 15:46
  • @geza 100% guessed it ... unfortunately – AMA Sep 05 '19 at 15:48
  • @AMA: you can use `union` for this purpose. It is still UB, but works with all major compilers (GCC documents that union can be used for this purpose. Clang is compatible with GCC and MSVC doesn't do alias-based optimization as far as I know). – geza Sep 05 '19 at 15:51
  • Can't you just overload `operator[]`? I don't really see the problem here... – Max Langhof Sep 05 '19 at 15:53
  • @MaxLanghof would you switch inside`[]`? I also wanted to provide `begin()/end()` – AMA Sep 05 '19 at 15:54
  • @geza yes, thank you. This was actually an attempt to get away from union + anonymous struct solution :) – AMA Sep 05 '19 at 15:55
  • @AMA: Let's hope that C++ some day will have something like C#'s property. Until then, there is no perfect solution for this problem. – geza Sep 05 '19 at 16:02
  • @AMA *"would you switch inside`[]`? "* In a similar class of mine, I do `*reinterpret_cast(reinterpret_cast(this) + sizeof(T) * index)`. I *think* this is well-defined, but I'm not completely sure (at least it avoids the UB cause mentioned by @geza). – HolyBlackCat Sep 05 '19 at 16:21
  • @HolyBlackCat: it should be: https://stackoverflow.com/questions/47498585/is-adding-to-a-char-pointer-ub-when-it-doesnt-actually-point-to-a-char-arr. Maybe a `std::launder` needed after the outer `reinterpret_cast`, though. – geza Sep 05 '19 at 16:30
  • I had this idea: https://codereview.stackexchange.com/questions/227525/convenient-vector3f-class – geza Sep 05 '19 at 17:41
  • The problem is even if you prove that the elements are contiguous, there isn't much you can do with that knowledge that isn't UB anyway. – François Andrieux Sep 05 '19 at 18:01

4 Answers4

4

Is it safe to assume that x, y, and z are in contiguous memory locations?

There is technically no such guarantee by the language.

On the other hand, there is no need for them to not be contiguous either, and they are quite likely to be contiguous in practice.

If not is it possible to enforce in a cross-platform way?

The cross-platform way of having objects that are guaranteed to be in contiguous memory locations is an array:

template <typename T>
struct Vector3d { T components[3]; };

Arrays also make it legal to use pointer arithmetic to iterate over the objects.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
eerorika
  • 232,697
  • 12
  • 197
  • 326
4

Is it safe to assume that x, y, and z are in contiguous memory locations?

The standard doesn't make such a guarantee.

But in practice, a sane implementation isn't going to insert any padding between adjacent fields of the same type (since such padding is never necessary1).

If you want extra safety, add a static_assert:

static_assert(sizeof(Vector3d<float>) == 3 * sizeof(float));

Is it at least safe to assume that for T = float and T = double?

From what I know, field type doesn't make any difference here.


1 — Arrays are guaranteed to contain no padding. Since you can make an array of any type, implementation has to be able to store objects of any single type next to each other with no padding.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
3

No there are absolutely no guarantees there is no padding between structure elements of the same types, even for "large" plain old data types such as a double. Furthermore the behaviour on attempting to reach an element by pointer arithmetic on a pointer to another element is undefined.

Far better to write

template <typename T>
struct Vector3d { T t[3]; };

where contiguity and pointer arithmetic are guaranteed, and provide access functions for x, y, and z.

If you don't like the syntax for calling functions, and are willing to tolerate some overhead that's most likely manifested in the struct itself, then you could always bind references:

template <typename T>
struct Vector3d
{
    T t[3];
    T& x = t[0];
    T& y = t[1];
    T& z = t[2];
};
Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • note that those references can't be optimised out, they will take space. – Sopel Sep 05 '19 at 16:39
  • 1
    @Sopel: And who gives a monkeys about that? OP has already said they can tolerate some space at the end. It's either that or use functions. – Bathsheba Sep 05 '19 at 16:40
  • For Vector3d on 64 bit it's 28 bytes of 'padding'. I'm just warning potential users. – Sopel Sep 05 '19 at 16:41
  • @Sopel: Yes, it's a wise caution. I'll make part of the answer. – Bathsheba Sep 05 '19 at 16:41
  • @Sopel You sure it's 28 bytes? I'd expect it to be a multiple of 3. – Bathsheba Sep 05 '19 at 16:43
  • 1
    3*4 + 4 padding to 8 byte boundary + 3*8 – Sopel Sep 05 '19 at 16:44
  • @Sopel: Thanks for this, (back in my box). – Bathsheba Sep 05 '19 at 16:44
  • @Sopel: Hmm. Are you sure that they can't be optimized away? What stops the compiler to remove the references (I mean, for this example, not in general)? – geza Sep 05 '19 at 17:13
  • https://stackoverflow.com/a/45418706/3763139, i'm not going to get deeper into this – Sopel Sep 05 '19 at 17:22
  • @Downvoters - (I never take downvotes personally), if I've slipped something then please let me know. – Bathsheba Sep 05 '19 at 17:36
  • @Sopel: that answer is irrelevant for this case. – geza Sep 05 '19 at 18:13
  • @FrançoisAndrieux: I asked it: https://stackoverflow.com/questions/57811230/is-it-a-missed-optimization-when-a-compile-time-known-reference-takes-space-in – geza Sep 05 '19 at 18:38
  • Well, one could use the references only when (if) needed, without adding them to the class. Those should be more likely [optimized away](https://godbolt.org/z/UNsnWZ). – Bob__ Sep 06 '19 at 11:52
-2

The only reason padding is ever added to structures and classes is to satisfy the alignment requirements of its members. Note that the alignment of a structure is the alignment of its member with the maximum alignment requirement. The size of a structure is a multiple of its alignment, trailing padding is added to satisfy that.

Since the members of Vector3d are of the same type the alignment requirements are already satisfied and, hence, there isn't any padding between the members or at the end of the structure.

There is no requirement in the C++ standard to insert arbitrary padding for no reason.

To be 100% sure, throw in a static_assert:

static_assert(sizeof(Vector3d<float>) == 3 * sizeof(float), "Unexpected layout.");
static_assert(sizeof(Vector3d<double>) == 3 * sizeof(double), "Unexpected layout.");

Some people say that the guarantee is not explicitly spelled out in the C++ standard and, hence, one cannot rely on that. However, the C++ standard is often under-specified, so that one needs to understand the rationale behind it. In my personal opinion, those people spread fear, uncertainty and doubt for no good reason.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • 3
    But a sufficiently evil (or poorly implemented) compiler would still be allowed to add padding. There's no *guarantee*. – Jesper Juhl Sep 05 '19 at 15:34
  • @JesperJuhl I don't think such a compiler would reach any widespread popularity. – Maxim Egorushkin Sep 05 '19 at 15:35
  • But if you strive to write *strictly correct* code that *only* relies on *guaranteed* behaviour (which you *should* IMHO), then you have to assume that there *could* be padding. – Jesper Juhl Sep 05 '19 at 15:36
  • I don't think this answer in its current form agrees with the C++ standard (even though the real world may be sufficiently "nice"). – Max Langhof Sep 05 '19 at 15:37
  • 1
    Wrong for `struct abc { char a, b, c; };` There will most certainly be a single byte padding on x86 and 5 bytes on x64, assuming chars take 1 byte. – Tanveer Badar Sep 05 '19 at 15:37
  • @MaximEgorushkin How so? There was a mistake for x64 in my comment. – Tanveer Badar Sep 05 '19 at 15:40
  • @TanveerBadar Verify your claims with a compiler. – Maxim Egorushkin Sep 05 '19 at 15:40
  • 2
    @TanveerBadar: there will be no padding for your example with all major compilers. – geza Sep 05 '19 at 15:41
  • @TanveerBadar `alignof(abc)` will likely be 1: https://wandbox.org/permlink/3fFk6bfhPB0yna57. – Daniel Langr Sep 05 '19 at 15:41
  • @MaxLanghof There is no requirement in the C++ standard to insert arbitrary padding for no reason. – Maxim Egorushkin Sep 05 '19 at 15:43
  • 7
    *"There is no requirement in the C++ standard to insert arbitrary padding for no reason."* There is also no requirement to NOT insert padding for no reason. – HolyBlackCat Sep 05 '19 at 15:43
  • @HolyBlackCat Software engineers are very good at imagining non-existing problems. – Maxim Egorushkin Sep 05 '19 at 15:44
  • 3
    @MaximEgorushkin There is no requirement in the C++ standard for UB to sometimes break your program. Yet that happens (and is explicitly _allowed_ by the standard, just not _required_). – Max Langhof Sep 05 '19 at 15:44
  • @MaxLanghof If you'd like to counter my claims come up with an example please, I am not interested in bickering about wording. – Maxim Egorushkin Sep 05 '19 at 15:46
  • 3
    @MaximEgorushkin *"good at imagining non-existing problems"* We truly are, but this passage (*"There is no requirement in the C++ standard..."*) looks misleading to me. It sounds like the standard guarantees the lack of padding, while it's not. – HolyBlackCat Sep 05 '19 at 15:46
  • @HolyBlackCat "_It sounds like_" - well, I cannot possibly be responsible for what it sounds like to someone, only for what I actually said. – Maxim Egorushkin Sep 05 '19 at 15:49