4
struct Data {
    int a;
    std::string b;
    float c;
};

std::string* allocateDataAndGetString() {
    Data* dataPtr(someAllocator.allocate<Data>());
    return &dataPtr.b;
}

Data* getBaseDataPtrFromString(std::string* mStringMember) {
    // ???
}

int main() {
    std::string* stringPtr(allocateDataAndGetString());
    Data* dataPtr(getBaseDataPtrFromString
}

I have a Data instance allocated on the heap, and a pointer to its std::string b; member. How do I get the base address of the Data instance the string is a member of, taking into account offsets and padding, in a standard way?

I've tried subtracting sizeof(int) and std::offsetof(Data, std::string) from the std::string* pointer, but I couldn't get it to work.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 2
    looks doable and... horrible. I would say just.. don't. Can you come up with a valid use case? – Karoly Horvath Feb 20 '14 at 13:57
  • @KarolyHorvath: I'm using it for a (work in progress) bidirectional map implementation http://pastie.org/8752175 – Vittorio Romeo Feb 20 '14 at 14:33
  • 1
    @Yakk Asuming that `ssvu::Uptr` is a flavor of `unique_ptr`, he's not using pointers to the elements, the elements *are* pointers. (I do note that `SneakyBimap::emplace` isn't exception safe - it will leak `pair` if `storage.emplace_back` throws. It's almost always a mistake to use an owning raw pointer.) – Casey Feb 20 '14 at 16:04
  • @Yakk, Casei: `ssvu::Uptr` is just an alias for `std::unique_ptr`. I don't undestand the *emplace mistake* - how can I fix it? – Vittorio Romeo Feb 20 '14 at 16:14
  • 1
    @VittorioRomeo Put the raw pointer in a `unique_ptr` before passing it to `storage.emplace_back` (a la `storage.emplace_back(ssvu::Uptr(pair))`). The easiest way is to use `make_unique` in the first place, so that the memory is always owned by some smart pointer: `auto pair = make_unique(std::forward(mArgs)...))`. – Casey Feb 20 '14 at 16:16
  • @Casei ah, I see. So the only problem is presuming standard layout? (`offsetof` supports non-POD in C++11?). Probably `get2From1` should look much like `get1From2` (harmless almost certainly?) I would replace `std::set` with `std::unordered_set` myself (unless you need order) and write `DerefHash` -- much faster. – Yakk - Adam Nevraumont Feb 20 '14 at 16:28
  • @Casey, thank you for the explanation. I have many instances of that issue in my codebase, will proceed to fix them all. @Yakk, what is `DerefHash`? – Vittorio Romeo Feb 20 '14 at 16:52

4 Answers4

5

Use offsetof from <cstddef>, but beware it is only defined on standard-layout types (Live at Coliru):

Data* getBaseDataPtrFromString(std::string* mStringMember) {
    static_assert(std::is_standard_layout<Data>::value,
                  "offsetof() only works on standard-layout types.");
    return reinterpret_cast<Data*>(
      reinterpret_cast<char*>(mStringMember) - offsetof(Data, b)
    );
}

offsetof is detailed in C++11 18.2/4:

The macro offsetof(type, member-designator) accepts a restricted set of type arguments in this International Standard. If type is not a standard-layout class (Clause 9), the results are undefined.195 The expression offsetof(type, member-designator) is never type-dependent (14.6.2.2) and it is value-dependent (14.6.2.3) if and only if type is dependent. The result of applying the offsetof macro to a field that is a static data member or a function member is undefined. No operation invoked by the offsetof macro shall throw an exception and noexcept(offsetof(type, member-designator)) shall be true.

and C99 (N1256) 7.17/3:

The macros are

NULL

which expands to an implementation-defined null pointer constant; and

offsetof(type, member-designator)

which expands to an integer constant expression that has type size_t, the value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning of its structure (designated by type). The type and member designator shall be such that given

static type t;

then the expression &(t.member-designator) evaluates to an address constant. (If the specified member is a bit-field, the behavior is undefined.)

The "restricted set of type arguments in this International Standard" in the C++ standard is there to draw your attention to the fact that offsetof is more restrictive than is the case for the C standard.

Casey
  • 41,449
  • 7
  • 95
  • 125
3

offsetof gives the offset in chars, so you need to cast to mStringMember to char * before doing the pointer arithmetic.

(Data*)((char*)mStringMember - offsetof(Data, b))
ooga
  • 15,423
  • 2
  • 20
  • 21
2

offsetof only works on standard-layout types.

There is a trick you might be able to use and does not require a standard layout: static_cast knows how to get to a more derived object from a pointer to one of its bases.

struct Data : private std::string {
private:
    using b_base = std::string;
public:
    // was int f() const { return b.size(); }
    int f() const { return b_base::size(); }

private:
    int a;
    float c;
    friend Data* getBaseDataPtrFromString(std::string* mStringMember);
};

Data* getBaseDataPtrFromString(std::string* mStringMember) {
    return static_cast<Data*>(mStringMember);
}
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
1

If you are sure that some ptr is in fact the address of some s->b (which is not always true) you might try to use offsetof:

Data* getBaseDataPtrFromString(std::string* ptr) {
   void* ad = (char*)ptr - offsetof(Data,b);
   return reinterpret_cast<Data*>(ad);
}

BTW, GCC has a builtin_offsetof to help implementing the offsetof macro (notably in more general cases than those required by the standard). See this question.

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547