0

In the following code:

using Previous = std::atomic<void*>;
template<class T>
struct Element{
    Previous previous;
    T value;
}

Am I allowed to do pointer arithmetic to get Element<T>* from T* ? Like this:

template<class T>
Element<T>* getElement(T* value){
   return static_cast<Element<T>*>(static_cast<void*>(reinterpret_cast<unsigned char *>(value) - sizeof(Previous)));
}

Where T may be non-standard layout.

P.S. I clearly see that I cannot use offsetof for this. But, may be for this special case, pointer arithmetic will work?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
tower120
  • 5,007
  • 6
  • 40
  • 88
  • 3
    **NO**: https://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member – NathanOliver Aug 15 '17 at 19:40
  • 7
    I recommend less hacking and more fixing the design. – Baum mit Augen Aug 15 '17 at 19:40
  • 1
    You're only allowed to do pointer arithmetic if the result is a pointer to an element belonging to the same array or one-past-the-end of that array. (`&x` behaves like a one-element array.) Everything else is undefined. – molbdnilo Aug 15 '17 at 19:55
  • What I really want is an `template Object* operator-(Member* member, Member Object::* ptr)` so you can subtract a pointer-to-member from a pointer to a member to get the pointer to the containing object. – Daniel H Aug 15 '17 at 20:00

1 Answers1

1

The answer is: It depends on the compiler!

If you have a 64-bit compiler and sizeof(Previous) is 4 and T is a pointer data type the compiler (at least most compilers) will add 4 additional bytes between previous and value.

The pointer you get using:

(void *)(((unsigned char *)value) - sizeof(Previous)

... would point to the first byte after previous in this case!

However you might replace sizeof(Previous) by the following expression:

(int)&(((Element *)NULL)->value)

(Sorry that the expression above is a C expression; my C++ is not the best.)

This expression should always be constant: The address offset of the element in the parent structure.


Edit

Theoretically there could be compilers that do a NULL pointer check which will result in an error when using the expression above.

In this case the following expression will do the job:

((int)&(x.value) - (int)&x)

... while x is any object (e.g. a local variable) of the data type Element.

Martin Rosenau
  • 17,897
  • 3
  • 19
  • 38
  • `(int)&(((Element *)NULL)->value)` - this will return offset between parent and value, right? – tower120 Aug 15 '17 at 19:52
  • @tower123: Yes, it should. At least in C it does. I don't know about all C++ variants; C++.NET for example will not allow you to do pointer arithmetic at all... – Martin Rosenau Aug 15 '17 at 19:54
  • You’d need to check the alignment of `T` to know how much padding was being introduced; you can’t assume it’s 4. – Daniel H Aug 15 '17 at 19:54
  • @DanielH I modified my answer. – Martin Rosenau Aug 15 '17 at 19:58
  • The second sentence still says it’ll add 4 bytes, which just isn’t true if `T` is `char`, `char[12]`, `uint32_t`, or any other type with 4-byte alignment or less. – Daniel H Aug 15 '17 at 20:02
  • @DanielH Thanks. I modified the answer again. – Martin Rosenau Aug 15 '17 at 20:06
  • https://stackoverflow.com/questions/26906621/does-struct-name-null-b-cause-undefined-behaviour-in-c11 dereference of a null pointer cause UB. – tower120 Aug 15 '17 at 20:23
  • @tower120 This is true. Theoretically there could be compilers that do a NULL pointer check. However I doubt that there are such compilers - at least not compilers that allow pointer arithmethic. – Martin Rosenau Aug 16 '17 at 06:15
  • @MartinRosenau `((int)&(x.value) - (int)&x)` is it guaranteed to have the same offset across different object instances of the same type? – tower120 Aug 17 '17 at 13:24
  • @tower120 Theoretically there could be C++ compilers which use different offsets. However I doubt there are such compilers. – Martin Rosenau Aug 17 '17 at 19:13