1

Within an array, pointer arithmetic is allowed (as long as the result doesn't exceed the bounds, where the end bound is one past the end of the array).

// Legit
int m[3] = {0, 1, 2};
int const *p = m;
std::cout << *p << *(p + 1) << *(p + 2) << std::endl;

Does the standard allow pointer arithmetic like this with a standard layout struct that contains consecutive member variables of the same fundamental type?

// Well defined???
struct MyStruct {
    int a, b, c;
};
static_assert(std::is_standard_layout_v<MyStruct>);

MyStruct  m = {0, 1, 2};
int const *p = &m.a;
std::cout << *p << *(p + 1) << *(p + 2) << std::endl;

I certainly can compare pointers to the individual variables (i.e., &m.a < &m.b) because they are members of the same object, but I cannot determine whether &m.a+1 is guaranteed to be &m.b.

I know this will likely work with most compilers, but I'm curious about what the standard says (C++20, if it matters).

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
  • 2
    I don't believe the standard guarantees this will work because the compiler is free to add padding between data members for reasons like alignment. But I don't know if there's any specific wording that says "this isn't guaranteed to work." – templatetypedef Jun 24 '21 at 17:36
  • _"...A pointer to non-array object is treated as a pointer to the first element of an array with size 1...."_ https://en.cppreference.com/w/cpp/language/operator_arithmetic and as you know dereferencing 1 past the end (or greater) is Undefined Behaviour. – Richard Critten Jun 24 '21 at 17:36
  • _"...I certainly can compare pointers..."_ no you can only compare pointers that point to the same object `m.a` and `m.b` are different objects. You can however use `std::less` to compare the pointers just not the builtin `operator<` – Richard Critten Jun 24 '21 at 17:40
  • No, there is no such requirement. If you want to treat elements of an object as elements of an array, put them in an array. – Pete Becker Jun 24 '21 at 17:41
  • 2
    Doesn't https://stackoverflow.com/questions/58428562/accessing-struct-data-members-via-pointer-arithmetic answer your question? – DL33 Jun 24 '21 at 17:56
  • Interesting... If you placed a `union` in the struct...or used a `pragma pack` would the answer change? – Matt Jun 24 '21 at 18:08
  • @RichardCritten: Thanks for that catch. I got fooled by my search engine. I queried for "c++ pointer relational operators" and the top hit was for C rather than C++, and that guarantee does exist in C. – Adrian McCarthy Jun 24 '21 at 19:11
  • @DL33: Yes, thanks. I failed to find that in my web searches. And SO's "similar questions" box seemed to think my question was about PHP even though I tagged it as C++. – Adrian McCarthy Jun 24 '21 at 19:13

1 Answers1

3

From section Additive operators

(Emphasis mine)

When an expression J that has integral type is added to or subtracted from an expression P of pointer type, the result has the type of P.

  • If P evaluates to a null pointer value and J evaluates to 0, the result is a null pointer value.
  • Otherwise, if P points to an array element i of an array object x with n elements the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) array element i + j [...]
  • Otherwise, the behavior is undefined.

Bottom line, no guarantees if the underlying type is not an array.

To your point, std::is_standard_layout_v provides guarantees regarding the layout itself, which can include guarantees for arrays contained in it, but it does not qualify the struct itself to be an array of elements, which the standard expects to use pointer arithmetics.

Adrien Leravat
  • 2,731
  • 18
  • 32
  • 1
    Note that this also forbids `&m.a < &m.b`. – bitmask Jun 24 '21 at 18:15
  • @bitmask Oh yes ?! Aren't member of a structs/classes supposed to be layout in the order of declaration ? ... ;) _(I would be surprised that this expression is not guaranteed to be true...)_ _(especially for standard layout types)_ Am I wrong ? And if yes, why ? Please... – Tenphase Jun 24 '21 at 18:23
  • @Tenphase Two different objects. Doesn't matter that they are members of a third non-array object. – bitmask Jun 24 '21 at 18:31
  • 1
    @bitmask No, that is allowed. From [expr.rel](https://timsong-cpp.github.io/cppwp/n4861/expr.rel#4.2) "If two pointers point to different non-static data members of the same object, ..., the pointer to the later declared member is required to compare greater provided the two members have the same access control ..." – 1201ProgramAlarm Jun 24 '21 at 18:40
  • @bitmask I'm a little lost on that one I must say... If standard layout type is trivially copyable _(it can be copied via memcpy)_, and members are layout in the order of declaration, the address of `a` in a `struct m` should be lower than the address of `b` within the same object... No ?! :o – Tenphase Jun 24 '21 at 18:42
  • @1201ProgramAlarm It reassures me... _:P_ Otherwise, how would it be possible to `memcpy` it ? How would one be able to make cross-language APIs... ;) – _(which I did quite often...)_ – Tenphase Jun 24 '21 at 18:45
  • @1201ProgramAlarm I stand corrected. – bitmask Jun 24 '21 at 20:43