18

While looking at Linux kernel's implementation of doubly linked circular lists, I've found following macro:

#define container_of(ptr, type, member) ({           \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

The way this works is that it returns pointer to structure given only address of one of its members:

struct blabla
{
    int value;
    struct list_head *list;
}

Thus you can get pointer to blabla (and get to "value") given only pointer to list. To my question, how would I make this as portable as possible (best case conforming to C89/C99?). Due to usage of typeof(), this is gcc only.

This is what I've got so far:

#define container_of(ptr, type, member) (                  \
                (type *) (char *)(ptr)-offsetof(type,member)\
                )

Is this snippet conforming to ISO standards (and thus should be able to be compiled on any conforming compiler)?

Tomas Pruzina
  • 8,397
  • 6
  • 26
  • 39
  • 1
    Note that you've lost some type checking, but I guess there's no way to keep that without `typeof`. – Mat Apr 22 '12 at 16:33

4 Answers4

20

As Ouah commented, the ({ ... }) statement expression is a GNU extension; you won't be able to use that. Your core expression is close to what's required, but doesn't have enough parentheses:

#define container_of(ptr, type, member) \
                      ((type *) ((char *)(ptr) - offsetof(type, member)))

That looks clean to me. It's only spread across two lines for SO.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 4
    Indeed, and it should be mentioned that the only reason for the Linux kernel's use of nonstandard C is improved compile-time error checking. – R.. GitHub STOP HELPING ICE Apr 22 '12 at 16:59
  • Sounds good to me, I think I'll merge both answers into my implementation snippet. Thank you. – Tomas Pruzina Apr 22 '12 at 20:54
  • Is this macros really portable? You cast pointer of arbitrary type to char pointer then do pointer arithmetic with char pointer and then cast the result to other pointer type. This looks quite hacky. Is it guaranteed to work on any ISO conforming compiler? – anton_rh May 31 '18 at 04:28
  • There are a couple of factors that mean it will work; if you wished, you could add a couple of intermediary `void *` casts into the sequence, but it isn't necessary on any extant implementation. One of the factors is [C11 §6.2.5 ¶28](https://port70.net/~nsz/c/c11/n1570.html#6.2.5p28) _A pointer to `void` shall have the same representation and alignment requirements as a pointer to a character type.48)_ and footnote 48 says: _The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions._ – Jonathan Leffler May 31 '18 at 04:37
  • The main significant difference is that you can't increment or decrement a `void *` (n standard C because there is no value for `sizeof(void)`. GCC treats `sizeof(void)` as `1`. – Jonathan Leffler May 31 '18 at 04:40
15

The macro is written the way it is to perfom a type check on ptr. It's possible to use a compound literal instead of the statement expression and fall back to a simple check for pointers instead of using __typeof__ if the compiler is not gcc-compatible:

#ifdef __GNUC__
#define member_type(type, member) __typeof__ (((type *)0)->member)
#else
#define member_type(type, member) const void
#endif

#define container_of(ptr, type, member) ((type *)( \
    (char *)(member_type(type, member) *){ ptr } - offsetof(type, member)))
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • +1: You've done well retaining the type checking of `typeof` when available, and yet being correct when it isn't. – Jonathan Leffler Apr 22 '12 at 20:59
  • Are these brackets fine though? {} – Tomas Pruzina Apr 22 '12 at 22:00
  • @AoeAoe: the braces are the crux of the matter - we do not cast `ptr`, but initialize a new value via C99 compound literal syntax; in particular, this means that only implicit conversions will be performed, which is how the type check works – Christoph Apr 22 '12 at 23:21
  • 2
    Just a minor suggestion: `void` should be changed to `const void`, so that if `ptr` is const-qualified, then the compiler won't complain about the discarding of the `const` qualifier. – Alexandros Sep 29 '13 at 22:09
3

ISO C90 compatible version with type check. (However, caveat: two evaluations of ptr!)

#define container_of(ptr, type, member) \
   ((type *) ((char *) (ptr) - offsetof(type, member) + \
              (&((type *) 0)->member == (ptr)) * 0))

struct container {
  int dummy;
  int memb;
};


#include <stddef.h>
#include <stdio.h>

int main()
{
  struct container c;
  int *p = &c.memb;
  double *q = (double *) p;
  struct container *pc = container_of(p, struct container, memb);
  struct container *qc = container_of(q, struct container, memb);
  return 0;
}

Test:

$ gcc -Wall containerof.c
containerof.c: In function ‘main’:
containerof.c:20:26: warning: comparison of distinct pointer types lacks a cast
containerof.c:20:21: warning: unused variable ‘qc’
containerof.c:19:21: warning: unused variable ‘pc’

We get the distinct pointer types warning for 26, but not 25. That is our diagnostic about pointers being misused.

I first tried placing the type check into the left hand side of a comma operator, gcc complains about that having no effect, which is a nuisance. But by making it an operand, we ensure that it is used.

The &((type *) 0)->member trick isn't well defined by ISO C, but it's widely used for defining offsetof. If your compiler uses this null pointer trick for offsetof, it will almost certainly behave itself in your own macro.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • So if the latter expression is not optimized away, the program will access the memory at `0 + offsetof(type, member)` and probably segfault. Right? – Jo So May 10 '17 at 10:57
  • `&((type *) 0)->member` - AFAIK, using deference expression for NULL pointer is UB, even if it doesn't produce actual memory access. – anton_rh May 31 '18 at 04:32
  • https://en.wikipedia.org/wiki/Offsetof: _While this implementation works correctly in many compilers, it has undefined behavior according to the C standard, since it involves a dereference of a null pointer (although, one might argue that no dereferencing takes place, because the whole expression is calculated at compile time)._ – anton_rh May 31 '18 at 05:08
  • @anton_rh If that `offsetof` implementation is actually found in the compiler, then it can't be undefined. If we find that a compiler is using that trick in its own header file, that more or less legitimizes use of that trick elsewhere (under that compiler). Unless it is policing such uses based on the provenance (which header the macro expansion comes from). – Kaz May 31 '18 at 17:16
  • @Kaz, this implementation is UB in general (with respect to the Standard), though it is not UB in your specific compiler. So it works in your compiler, but might not work in other compilers. Compiler vendors usually write code (of their libraries) that works only with their compilers. That's why some features of the Standard libraries can't be implemented in pure C (the code, that doesn't use compiler extensions). – anton_rh Jun 02 '18 at 09:19
  • @anton_rh Can you name three compilers in which it doesn't work? – Kaz Jun 03 '18 at 04:35
  • See my answer: key point is that you don't need to compute anything in run time. And any expressions computable in compile time might be computed in the body of sizeof expression. – Kirill Frolov Dec 11 '21 at 19:26
1

Yes, you can make "container_of" macros to be strictly ISO C conforming. To do this you need two things:

  1. take rid of GNU exensions;

  2. find a way to check types compatibility.

Basically, types checking is not run time operation, but compile time rather. And I not see any reasons, why original "container_of" implementation creates new variable just to assign it and perform type checking. This can be done without creation of new variable in some expression which is only computed (and types checked) in compile time. Fortunately, we have no much options in C and only choice is to use "sizeof(expression)" to check the type. See an example:

#define container_of(ptr, type, member) \
    ( (void)sizeof(0 ? (ptr) : &((type *)0)->member), \
      (type *)((char*)(ptr) - offsetof(type, member)) )

In first line types compatibility is checked (for ternary operator compiler must insure, that types might be converted to common type, or that both types are compatible). Second line is the same, as in original "container_of" macros.

You can play with test program on GodBolt (https://godbolt.org/z/MncvzWfYn) and make sure, that this ISO conforming variant works even in Microsoft's Visual Studio compiler.

PS: After some time, I found that the following variant can be better:

       #define CONTAINER_OF(ptr, type, member) \
       ( (void)sizeof(0 ? (ptr) : &((type*)0)->member), \
         (typeof(_Generic((typeof(ptr))0, const typeof(*(typeof(ptr))0)*: (const type*)0, default: (type*)0))) \
               ((uintptr_t)(const void*)(ptr) - offsetof(type, member)) )

The difference, is that it preserves const qualifier from ptr and assigns it to the result, for example:

  • if ptr argument is const struct * pointer, the result then will have a type of const type *, despite if the type is const or not;
  • if ptr argument is non-const pointer (struct*), the result then will have type type*, which may be const or non-const, depending on the type of type argument.

As a result, preserving const qualifier reduces the possibility of the errors, when const pointer to some structure translated into non-const pointer via container_of macro.

Unfortunately, this version requires C23 or non-standard typeof() operator for earlier versions of C standard.

Another reason for ISO-compiant container_of macro, as opposed to implementation from Linux kernel, is that latter uses "statement expression" GCC-extension which works badly in a case, when argument ptr is a temporary variable. The latter might happen, when container_of macro is applied to the result of a function invocation (container_of(func().x, struct y, m), here is assumed, that func() returns a structure in which x is an array of structures), or to the compound statement (container_of((&(struct S){...}), struct B, m)). In both of these cases, a call to container_of macro borrowed from linux will result in a dangling pointer! This happens because a temporary object passed as ptr argument will be destroyed after the first semicolon (at the first line of Linux implementation of container_of macro), and because a variable created by a compound statement expression will be destroyed at the end of the nearest block, which is "statement expression" itself. ISO-compliant implementation of container_of macro have no such issues.

Kirill Frolov
  • 401
  • 4
  • 10
  • the macro could be slightly simplified to `((type*)((char*)(1 ? (ptr) : &((type*)0)->member) - offsetof(type, member)))`. The change will let it be used in constant expressions that be be used to initialize static objects. – tstanisl Jan 31 '23 at 13:56