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

标签 c++ c

例如,在 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<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
}

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

所以我的问题是:

  1. 是否至少有一个 CONTAINING_RECORD aka container_of 宏实现符合标准?

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

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

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

最佳答案

  1. 不,您提供的没有一个是合规的。取消引用空指针不是,typeof 不是,({ ... }) 表达式不是。

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

  3. 见2

AFAIK,所有这些都适用于 C 和 C++

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

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

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

但这只会告诉您 *ptr 的类型是否与 member 兼容。例如,如果 ptrfloat* 并且成员 double 这仍然有效。

无论如何,类型检查只会给你虚假的安全感。您必须非常确定您的 ptr 确实来自 type 内部才能使用它。小心。

编辑:正如 bert-jan 在他下面的评论中所说,在 C++ 中,当存在虚拟继承时,offsetof 宏存在一个根本问题,即成员的偏移量无法在编译时确定。我不相信容器宏的当前想法在这种情况下有任何意义。 要格外小心。

关于c++ - 使用指向在结构内声明的字段的指针(又名 CONTAINING_RECORD 宏)计算指向整个结构的指针的可移植方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8240273/

相关文章:

c++ - TinyXML2 从节点和所有子节点获取文本

c - 是否有任何 C 标准中定义的通用 printf-ish 例程

c - longjmp,setjmp 的无限循环

c - 从父进程到子进程并返回的管道

C++ 将字符串误认为字符

c++ - operator.*() 是做什么的?

c++ - OpenCv:车牌识别

c++ - 避免对临时对象、右值析构函数进行不必要的分配

c++ - PDCurses TUI C++ Win32 控制台应用程序 - 访问冲突读取位置

C 数组重新初始化