Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A portable way to calculate pointer to the whole structure using pointer to a field declared inside the structure (aka CONTAINING_RECORD macro)

Tags:

c++

c

There is the well known CONTAINING_RECORD() macro defined, for instance, in Winnt.h:

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

or in FreeBSD:

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

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

and, surely, in many other places worldwide.

However, I doubt they are standard compliant.

Boost sources (boost_1_48_0/boost/intrusive/detail/parent_from_meber.hpp) rather disapoint me - they have 3 #ifdef PARTICULAR_COMPILER cases:

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
}

The second case (#if defined GNUC and others) ) seems most common but I'm not sure that pointer arithmetic with "zero initalized" parent is well-defined (?)

So my questions are:

  1. Is at least one of CONTAINING_RECORD aka container_of macro implementations standard compliant?

  2. If not, does there exist a standard compliant way to calculate pointer to the whole structure using pointer to a field declared inside the structure?

  3. If not, does there exist a practically portable way to do it?

If answers differ for C and C++, I'm interested in both cases.

like image 416
user396672 Avatar asked Nov 23 '11 10:11

user396672


1 Answers

  1. No, none of the one's you have given is compliant. Dereferencing a null pointer isn't, typeof isn't and ({ ... }) expressions aren't.

  2. yes, the second part of the linux thing rewritten properly (type *)((char *)(ptr) - offsetof(type, member)) is compliant. (offsetof is defined in the standard)

  3. see 2

AFAIK, all this is valid for C and C++

Edit: The linux thing tries to add additional security to the macro by checking if a pointer to the member and the pointer in the argument are assignment compatible. As far as I can see the gcc extension typeof is essential for such an approach in C.

With C99, you could though use a "compound literal" to have a somewhat weaker check by doing something like

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

but this would only tell you if the type of *ptr is assignment compatible to member. E.g if ptr would be float* and member double this would still work.

In any case, the check of the types only gives you false security. You must be pretty sure that your ptr really comes from inside type to use this. Be careful.

Edit: As bert-jan remarks in his comments below, in C++, when there is virtual inheritance, the offsetof macro has the fundamental problem that the offset of member cannot be determined at compile time. I am not convinced that the present idea of a container macro then makes any sense in such a context. Be extremely careful.

like image 160
Jens Gustedt Avatar answered Sep 30 '22 22:09

Jens Gustedt