Struct offsets and pointer safety in C++

2k Views Asked by At

This question is about pointers derived using pointer arithmetic with struct offsets.

Consider the following program:

#include <cstddef>
#include <iostream>
#include <new>

struct A {
  float a;
  double b;
  int c;
};

static constexpr auto off_c = offsetof(A, c);

int main() {
  A * a = new A{0.0f, 0.0, 5};
  char * a_storage = reinterpret_cast<char *>(a);
  int * c = reinterpret_cast<int *>(a_storage + off_c));

  std::cout << *c << std::endl;

  delete a;
}

This program appears to work and give expected results on compilers that I tested, using default settings and C++11 standard.

(A closely related program, where we use void * instead of char * and static_cast instead of reinterpret_cast, is not universally accepted. gcc 5.4 issues a warning about pointer arithmetic with void pointers, and clang 6.0 says that pointer arithmetic with void * is an error.)

Does this program have well-defined behavior according to the C++ standard?

Does the answer depend on whether the implementation has relaxed or strict pointer safety ([basic.stc.dynamic.safety])?

3

There are 3 best solutions below

4
On

There are no fundamental errors in your code.

If A isn't plain old data, the above is UB (prior to C++17) and conditionally supported (after C++17).

You might want to replace char* and int* with auto*, but that is a style thing.

Note that pointers to members do this exact same thing in a type-safe manner. Most compilers implement a pointer to member ... as the offset of the member in the type. They do, however, work everywhere even on non-pod structures.

Aside: I don't see a guarantee that offsetof is constexpr in the standard. ;)

In any case, replace:

static constexpr auto off_c = offsetof(A, c);

with

static constexpr auto off_c = &A::c;

and

  auto* a_storage = static_cast<char *>(a);
  auto* c = reinterpret_cast<int *>(a_storage + off_c));

with

  auto* c = &(a->*off_c);

to do it the C++ way.

1
On

It is safe in your specific example, but only because your struct is a standard layout, which you can double-check using std::is_standard_layout<>.

Trying to apply this to a struct such as:

struct A {
  float a;
  double b;
  int c;
  std::string str;
};

Would be illegal, even if the string is past the part of the struct that's relevant.

Edit

Heres what im concerned abt: in 3.7.4.3 [basic.stc.dynamic.safety] it says pointer is safely derived only if (conditions) and if we have strict pointer safety then a pointer is invalid if it doesnt come from such a place. In 5.7 pointer arithmetic, it says i can do usual arithmetic within an array but i dont see anything there telling me struct offset arithmetic is ok. Im trying to figure out if this isnt relevant in the way i think it is, or if struct offset arithmetic is not ok in the hypothetical "strict" impls, or if i read 5.7 wrong (n4296)

When you are doing the pointer arithmatic, you are performing it on an array of char, the size of which is at least sizeof(A), so that's fine.

Then, when you cast back into the second member, you are covered under (2.4):

— the result of a well-defined pointer conversion (4.10, 5.4) of a safely-derived pointer value;

2
On

you should examine your assumptions.

Assumption #1) offsetof gives the correct offset in bytes. This is only guaranteed if the class is considered "standard-layout", which has a number of restrictions, such as no virtual methods, avoids multiple inheritances, etc. In this case, it should be fine, but in general you can't be sure.

Assumption #2) A char is the same size as a byte. In C, this is by definition, so you are safe.

Assumption #3) The offsetof gives the correct offset from the pointer to the class, not from the beginning of the data. This is basically the same as #1, but a vtable could certainly be a problem. Again, only works with standard-layout.