一种可移植的方法,使用指向结构内声明的字段的指针计算指向整个结构的指针(又名CONTAINING_RECORD宏)

use*_*672 6 c c++

例如,在Winnt.h中定义了众所周知的CONTAINING_RECORD()宏:

#define CONTAINING_RECORD(address, type, field) ((type *)( \
                                              (PCHAR)(address) - \
                                              (ULONG_PTR)(&((type *)0)->field)))
Run Code Online (Sandbox Code Playgroud)

或者在FreeBSD中:

#define CONTAINING_RECORD(addr, type, field)    \
      ((type *)((vm_offset_t)(addr) - (vm_offset_t)(&((type *)0)->field)))
Run Code Online (Sandbox Code Playgroud)

或者在Linux中:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({                      \
      const typeof(((type *)0)->member) * __mptr = (ptr);     \
      (type *)((char *)__mptr - offsetof(type, member)); })
Run Code Online (Sandbox Code Playgroud)

而且,当然,在全世界许多其他地方.

但是,我怀疑它们是否符合标准.

提升源(boost_1_48_0/boost/intrusive/detail/parent_from_meber.hpp)让我失望 - 他们有3个#ifdef PARTICULAR_COMPILER案例:

template<class Parent, class Member>
inline std::ptrdiff_t offset_from_pointer_to_member(const Member Parent::* ptr_to_member)
{
   //The implementation of a pointer to member is compiler dependent.
   #if defined(BOOST_INTRUSIVE_MSVC_COMPLIANT_PTR_TO_MEMBER)
   //msvc compliant compilers use their the first 32 bits as offset (even in 64 bit mode)
   return *(const boost::int32_t*)(void*)&ptr_to_member;
   //This works with gcc, msvc, ac++, ibmcpp
   #elif defined(__GNUC__)   || defined(__HP_aCC) || defined(BOOST_INTEL) || \
         defined(__IBMCPP__) || defined(__DECCXX)
   const Parent * const parent = 0;
   const char *const member = reinterpret_cast<const char*>(&(parent->*ptr_to_member));
   return std::ptrdiff_t(member - reinterpret_cast<const char*>(parent));
   #else
   //This is the traditional C-front approach: __MWERKS__, __DMC__, __SUNPRO_CC
   return (*(const std::ptrdiff_t*)(void*)&ptr_to_member) - 1;
   #endif
}
Run Code Online (Sandbox Code Playgroud)

第二种情况(#if定义了GNUC和其他)似乎最常见,但我不确定"零初始化"父级的指针算法是否定义明确(?)

所以我的问题是:

  1. CONTAINING_RECORD和container_of宏实现中的至少一个是否符合标准?

  2. 如果没有,是否存在一种符合标准的方法来计算指向整个结构的指针,使用指向结构内声明的字段的指针?

  3. 如果没有,是否存在实际可移植的方式?

如果C和C++的答案不同,我对这两种情况都很感兴趣.

Jen*_*edt 6

  1. 不,你给的那个人都不合规.取消引用空指针不是,typeof不是,({ ... })表达式不是.

  2. 是的,正确重写的linux事物的第二部分(type *)((char *)(ptr) - offsetof(type, member))是合规的.(offsetof在标准中定义)

  3. 见2

AFAIK,所有这些对C和C++都有效

编辑: linux的事情是通过检查指向成员的指针和参数中的指针是否与赋值兼容来尝试为宏添加额外的安全性.据我所知,gcc扩展typeof对于C中的这种方法至关重要.

使用C99,您可以使用"复合文字"通过执行类似操作来进行稍微弱一点的检查

(type){ .member = *(ptr) }
Run Code Online (Sandbox Code Playgroud)

但这只会告诉你类型是否与*ptr赋值兼容member.例如,如果ptrfloat*和成员double这仍然有效.

在任何情况下,检查类型只会给你错误的安全性.你必须非常确定你ptr真的来自内部type才能使用它.小心.

编辑:正如下面的评论中的bert-jan评论,在C++中,当存在虚拟继承时,offsetof宏具有基本问题,即成员的偏移量在编译时无法确定.我不相信容器宏的现有想法在这样的背景下是有意义的.要非常小心.

  • 只是评论.C++(I举n3242)限制offsetof:"如果结构类型不是一个标准布局类(第9节),结果是不确定的",所以为非标准布局结构(具有混合私人/公共字段,虚函数等)标准不保证宏偏移的正确性.(虽然_may_可以在除虚拟后代之外的其他情况下工作) (2认同)