从成员中查找struct的基址的标准方法

时间:2014-02-20 13:51:50

标签: c++ c++11 struct memory-address offsetof

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
}

我在堆上分配了一个Data实例,以及一个指向其std::string b;成员的指针。如何以标准方式获取字符串所属的Data实例的基址,并考虑偏移和填充?

我尝试从sizeof(int)指针中减去std::offsetof(Data, std::string)std::string*,但我无法让它工作。

4 个答案:

答案 0 :(得分:5)

使用offsetof中的<cstddef>,但请注意,它仅在标准布局类型(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详见C ++ 11 18.2 / 4:

  

offsetof type member-designator )在本国际标准中接受一组受限制的类型参数。如果 type 不是标准布局类(第9条),则结果未定义。 195 表达式offsetof type member-designator )从不依赖于类型(14.6.2.2),并且当且仅当 type 依赖时,它才是值依赖的(14.6.2.3)。将offsetof宏应用于作为静态数据成员或函数成员的字段的结果是未定义的。 offsetof宏调用的任何操作都不会抛出异常,noexcept(offsetof(type, member-designator))应为true

和C99(N1256)7.17 / 3:

  

宏是

NULL
     

扩展为实现定义的空指针常量;和

offsetof(type, member-designator)
     

扩展为一个整型常量表达式,其类型为size_t,其值是以字节为单位的偏移量,由结构成员(由 member-designator 指定),来自结构的开头(由 type 指定)。类型和成员指定符应为

static type t;
     

然后表达式&(t. member-designator )计算为地址常量。 (如果指定的成员是位字段,则行为未定义。)

C ++标准中的“本国际标准中的限制类型参数集”是为了引起您注意offsetof比C标准更具限制性的事实。

答案 1 :(得分:3)

offsetof给出了字符中的偏移量,因此在进行指针运算之前,需要将mStringMember强制转换为char *

(Data*)((char*)mStringMember - offsetof(Data, b))

答案 2 :(得分:2)

offsetof仅适用于标准布局类型。

您可以使用一种技巧,并且不需要标准布局:static_cast知道如何从指向其中一个基础的指针获取更多派生对象。

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);
}

答案 3 :(得分:1)

如果您确定某些ptr实际上是某些s->b的地址(并非总是如此),您可能会尝试使用offsetof

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

BTW,GCC有一个builtin_offsetof来帮助实现offsetof宏(特别是在比标准要求的更一般的情况下)。请参阅this question