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

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

#define CONTAINING_RECORD(address, type, field) ((type *)( \ (PCHAR)(address) - \ (ULONG_PTR)(&((type *)0)->field))) 

或者在FreeBSD中:

 #define CONTAINING_RECORD(addr, type, field) \ ((type *)((vm_offset_t)(addr) - (vm_offset_t)(&((type *)0)->field))) 

或者在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)); }) 

而且,当然,在世界上许多其他地方。

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

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

 template 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(&(parent->*ptr_to_member)); return std::ptrdiff_t(member - reinterpret_cast(parent)); #else //This is the traditional C-front approach: __MWERKS__, __DMC__, __SUNPRO_CC return (*(const std::ptrdiff_t*)(void*)&ptr_to_member) - 1; #endif } 

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

所以我的问题是:

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

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

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

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

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

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

  3. 见2

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

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

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

 (type){ .member = *(ptr) } 

但这只会告诉你*ptr的类型是否与member兼容。 例如,如果ptr将是float*并且成员double那么它仍然可以工作。

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

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

仅当’type’是不使用多重inheritance的类型时,确定包含结构的地址的方法才有效。 否则,宏会取消引用NULL指针,这总是有问题。 我曾经问过一个关于这个问题的问题( 这是有效的ANSI C ++代码吗?试图在编译时生成结构成员的偏移量 )。