【问题标题】:Get pointer to object from pointer to some member从指向某个成员的指针获取指向对象的指针
【发布时间】:2016-02-25 12:15:25
【问题描述】:

假设有一个结构

struct Thing {
  int a;
  bool b;
};

我得到一个指向该结构的成员b 的指针,比如某个函数的参数:

void some_function (bool * ptr) {
  Thing * thing = /* ?? */;
}

如何获得指向包含对象的指针? 最重要的是:在不违反标准中的某些规则的情况下,我想要标准定义的行为,而不是未定义或实现定义的行为。

附带说明:我知道这会绕过类型安全。

【问题讨论】:

  • bool 地址中扣除您的Thing 指针很棘手。为什么不将事物地址/指针传递给您的函数?
  • @Stefan 它应该是分配器的一部分:它有带有簿记和数据的节点,我在分配时分发一个指向数据的指针,然后在解除分配时将其取回。
  • 我刚刚注意到您在这里关于分配器中的评论。这意味着您不能保证它是标准布局,并且offsetof 可以是未定义的。自 2015 年 11 月以来,您可能已经从这里开始,但如果它仍然相关并且出现非offsetof 答案,我建议您实施它。幸运的是,对于分配器,您不需要返回指针。您可以将满足NullablePointerRandomAccessIteratortypedef 的任何类似指针的结构返回给pointer
  • 只是想知道:如果您需要从某个成员获取对象 - 为什么不能简单地将对象作为指针而不是成员传递?

标签: c++ pointers c++11 language-lawyer


【解决方案1】:

如果您确定指针确实指向结构中的成员b,就像有人这样做

Thing t;
some_function(&t.b);

那么您应该可以使用offsetof 宏来获取指向该结构的指针:

std::size_t offset = offsetof(Thing, b);
Thing* thing = reinterpret_cast<Thing*>(reinterpret_cast<char*>(ptr) - offset);

请注意,如果指针ptr 没有实际指向Thing::b 成员,那么如果您使用指针thing,上述代码将导致未定义的行为。

【讨论】:

  • AFAIK char* 对于这种指针算法来说是完全安全和规范的方式。
  • 好吧,那里太糊涂了 :) char 不能小于 8 位,因为最小范围,如果超过 8 位,int8_t 甚至不存在,因为地址和大小的东西...一切都很好。
  • “这是使用int8_t 的实际意义,它被定义为char 可能不是的字节”——不,你完全搞错了。这里使用charint8_t是错误的。
  • 另请注意,offsetof 对于非标准布局类型 (C++11) 具有未定义的行为。在实践中,它通常仍能按预期工作。
  • 术语“标准布局”大致意思是“不使用 C 没有的任何功能的东西”。事实证明offsetof 实际上是一个 C 特性,而不是 C++ 特性,并且 C++ 规范明确表示它在非标准布局的类上未定义。另请参阅 cppreference 上的 std::is_standard_layoutoffsetofthe Standard Layout section of the non-static data members page
【解决方案2】:
void some_function (bool * ptr) {
  Thing * thing = (Thing*)(((char*)ptr) - offsetof(Thing,b));
}

认为没有UB。

【讨论】:

    【解决方案3】:
    X* get_ptr(bool* b){
        static typename std::aligned_storage<sizeof(X),alignof(X)>::type buffer;
    
        X* p=static_cast<X*>(static_cast<void*>(&buffer));
        ptrdiff_t const offset=static_cast<char*>(static_cast<void*>(&p->b))-static_cast<char*>(static_cast<void*>(&buffer));
        return static_cast<X*>(static_cast<void*>(static_cast<char*>(static_cast<void*>(b))-offset));
    }
    

    首先,我们创建一些可以保存X的静态存储。然后我们得到可能存在于缓冲区中的X对象的地址,以及该对象的b元素的地址。

    回溯到char*,因此我们可以得到bool在缓冲区中的偏移量,然后我们可以使用它来将指向真实bool的指针调整回指向包含X的指针.

    【讨论】:

    • 您的意思是将buffer 声明为static typename std::aligned_storage&lt;sizeof(X),alignof(X)&gt;::type?另外,有理由static_castvoid* 而不是简单地reinterpret_casting 直接到char*X* 吗?
    • 是的,很清楚。我不喜欢reinterpret_cast,因为映射不明确。 static_cast 具有已定义的映射。
    • 我想这通常是有道理的,尽管reinterpret_cast 用于指针的定义很明确。我不喜欢这样保留了额外的空间,但除此之外它看起来不错。我非常担心这个X 和参数一之间的偏移量不一样,但是如果编译器这样做(我找不到任何证据它不能,对于非标准布局,但我怀疑是否有),我不确定有什么办法可以绕过它。
    • 很抱歉我昨天没有及时到这里分配全部赏金;这似乎仍然是最好的解决方案。虽然我确实喜欢 @rfb 为其制作通用函数,但由于对齐问题,我不确定它是否符合标准。
    • 对不起,但如果我的回答有价值,我认为这不是通用的,而是因为它提出了类数据成员指针给出的内置置换机制的想法。我的解决方案与@AnthonyWilliams 的响应一致,它不是使用一个存储,而是使用这种偏移量的知识。实际上,我从中确认的主要帖子围绕着在没有临时实例的情况下从成员指针获取偏移量的问题。
    【解决方案4】:

    我的建议来自Offset from member pointer without temporary instance 中的@Rod 答案,以及Offset of pointer to member 中类似的@0xbadf00d 答案。

    我开始设想一种偏移形式来驱动指向类数据成员的指针的实现,后来通过相关帖子和我所做的测试证实了这一点。

    我不是 C++ 的从业者,所以很抱歉。

    #include <iostream>
    #include <cstddef>
    
    using namespace std;
    
    struct Thing {
        int a;
        bool b;
    };
    
    template<class T, typename U>
    std::ptrdiff_t member_offset(U T::* mem)
    {
        return 
        ( &reinterpret_cast<const char&>( 
            reinterpret_cast<const T*>( 1 )->*mem ) 
          - reinterpret_cast<const char*>( 1 )      );
    }
    
    template<class T, typename U>
    T* get_T_from_data_member_pointer (U * ptr, U T::*pU) {
      return reinterpret_cast<T*> (
          reinterpret_cast<char*>(ptr) 
        - member_offset(pU));
    }
    
    int main()
    {
    
        Thing thing;
        thing.b = false;
    
        bool * ptr = &thing.b;
        bool Thing::*pb = &Thing::b;
    
        std::cout << "Thing object address accessed from Thing test object lvalue; value is: " 
            << &thing << "!\n";     
        std::cout << "Thing object address derived from pointer to class member; value is: " 
            << get_T_from_data_member_pointer(ptr, &Thing::b) << "!\n";    
    }
    

    【讨论】:

    • 是否使用1 作为指针实际上符合标准,或者它可能有对齐问题或其他什么?我实际上认为您不允许将任何 int 转换为指针,除非该值是从最初的一组有限的操作 from 指针中计算出来的,尽管您可能不是允许取消引用。虽然我在研究这个问题时确实想过很多次,但我希望 C++ 默认为所有类型 TU 提供一个 T* operator-(U*, U T::*)
    • 或者至少 gcc 将其作为扩展提供,因为我很确定它实际上将指向数据成员变量的指针存储为 ptrdiff_t (refspecs.linuxfoundation.org/cxxabi-1.86.html)。
    • 如果我从我对 C++98 的旧研究中没记错的话,我认为不会像大多数 reainterpret_cast 问题一样合规。但是转换很棘手,因为它只是一种推断位移的方法,因为类数据成员指针是通过偏移/位移实现的。它只是为计算指针算术提供了基础,没有什么可以取消引用的。如果我错了纠正我。 @Rod 的答案指出了这种特殊的复杂语法(首先是字符引用,然后是地址)是由于您的观察行中的编译器警告造成的。
    猜你喜欢
    • 2011-05-17
    • 1970-01-01
    • 2012-02-10
    • 2023-04-04
    • 2018-03-24
    • 2014-01-02
    • 2021-12-22
    • 1970-01-01
    相关资源
    最近更新 更多