6

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.

undur_gongor
  • 15,657
  • 5
  • 63
  • 75
user396672
  • 3,106
  • 1
  • 21
  • 31

2 Answers2

8
  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.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • All the ways are valid if you have an object. If you don't have an object (for instance if you are trying to calculate offsets at compile-time and want to use those as comiple-time constants) you are limmited in your methods to do so, and compliance issues arrise. – bert-jan Nov 23 '11 at 10:44
  • @bert-jan, I am not sure that I understand you correctly. All the methods as given by the OP are invalid code for a user program because they all derefference a null pointer. The `offsetof` macro as given in the Linux example is only valid if is part of the platform headers that come with the compiler and if the compiler vendor can guarantee that it works correctly. (A compiler vendor has greater liberty in implementing than a user of the compiler has). BTW I don't know where that version of the macro comes from, usually Linux relies on gcc, and gcc does `offsetof` differently, IIRC. – Jens Gustedt Nov 23 '11 at 11:12
  • @Jens Gustedt I omitted original #ifndef in Linux case: actually there are #ifndef offsetof #define offsetof... #endif, so the code may work with or without standard gcc offsetof – user396672 Nov 23 '11 at 11:31
  • 1
    I would have to check, but there was some discussion about that when defining the standard. In particular as to whether `&*p` with `p` being a null pointer should be UB or not (the compiler does not really *dereference* any pointer). Maybe someone else remembers the whole discussion or the outcome. – David Rodríguez - dribeas Nov 23 '11 at 11:31
  • 1
    @Jens Gustedt , All the compilers I used, accept the offsetof(TYPE, MEMBER) as a compile-time constant. If it is portable, I don't know, but if the compiler accept the offsetof macro as a constant, then it is safe to use the macro. All the macro does is: suppose there is an object at address 0, what would be the address of the member? By taking it's address, the compiler does not load the value of the member (e.g. access the memory near address 0) unless the actual type of the containing object is a type that has a virtual base class. – bert-jan Nov 23 '11 at 11:52
  • @David I found this discussion: http://stackoverflow.com/questions/2896689/dereferencing-the-null-pointer However, it covers a special case: &*null_ptr. For this case it is explicit statement in C99 standard; for C++ it is formally undefined behaviour (if I properly understand the "outcome" :). However, it seems this C99 statement is not applicable to (&null_ptr->member) case. (maybe there are some other discusions, however) – user396672 Nov 23 '11 at 12:14
  • @DavidRodríguez-dribeas, `&` and `*` applied to a object would cancel out without actually derefferencing. AFAICS this is not relevant here. The things that are used here don't have a `*` operator. They are taking the address of an object that obviously doesn't exist. Even worse, a null pointer in C can be implemented differently than by having "all bits zero". – Jens Gustedt Nov 23 '11 at 12:19
  • @bert-jan, as I already mentionned, `offsetof` is part of the C standard (and also of C++, I think). And yes, it is guaranteed exist for every implementation and to provide a compile time constant. But the question of how your compiler vendor implements this is voluntarily left out of the standard. The things we have seen here are *possible* implementations, but this depends on the compiler. It is not you as a programmer who choses an implementation of that macro to your liking, if you want to write portable code. – Jens Gustedt Nov 23 '11 at 12:23
  • @Jens Gustedt , offsetof macro can be used without having an object. If, and only if, the provided type is one that uses virtual inherritance, an object must be provided in order to calculate the address of a member variable declared in the virtual base class. As I stated earlier, all the solutions provided by the OP work. If the standard requires the offsetof macro to be present, then using the offsetof macro must be portable. So, given the fact that the TYPE parameter does not refere to a type with virtual base class, it works on all platforms, hence it is portable. – bert-jan Nov 23 '11 at 12:51
  • @bert-jan, I am not sure what you are trying to prove, but you seem to be worried of particular aspects inheritance in C++? Only the Linux version given by the OP uses the `offsetof` macro. And this is exactly what I am saying in my answer, you should use the `offsetof` macro instead of handcrafted replacements. – Jens Gustedt Nov 23 '11 at 13:37
  • @Jens Gustedt , The offsetof macro itself is not portable as defined in standard library. It interfers badly with virtual inheritance. The guys at ANSI know about this issue and are thinking to do something on this little 'shortcomming' of their standard. If there is no virtual inheritance, there is no problem. – bert-jan Nov 23 '11 at 13:51
  • 2
    just a comment. C++ (I cite n3242) restricts offsetof: "If the structure type is not a standard-layout class (Clause 9), the results are undefined" so for non-standard-layout structs (with mixed private/public fields, virtual functions, etc.)the correctness of the macro offsetof is not guaranteed by the standard. (Although it _may_ work in other cases except of virtual descendants) – user396672 Nov 23 '11 at 17:37
0

Your ways to determine the address of the containing structure work only if 'type' is a type that does not use multiple inherritance. Otherwise, the macros would dereference the NULL pointer, and that is always guaranteed trouble. I once asked a question about this matter ( Is this valid ANSI C++ code? Trying to generate offsets of structure members at compile-time ).

Community
  • 1
  • 1
bert-jan
  • 958
  • 4
  • 15
  • 1
    (apart of standard compliance issues) I don't see any real troubles with multiple inheritance, only with _virtual_ inheritance (indeed, in this case offset of virtual base class is undefined at complile time) – user396672 Nov 23 '11 at 11:23