By pruzinat


2012-04-22 16:17:24 8 Comments

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)?

3 comments

@Kaz 2012-04-24 02:12:35

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.

@Jo So 2017-05-10 10:57:20

So if the latter expression is not optimized away, the program will access the memory at 0 + offsetof(type, member) and probably segfault. Right?

@anton_rh 2018-05-31 04:32:11

&((type *) 0)->member - AFAIK, using deference expression for NULL pointer is UB, even if it doesn't produce actual memory access.

@anton_rh 2018-05-31 04:35:54

@anton_rh 2018-05-31 05:08:20

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

@Kaz 2018-05-31 17:16:21

@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).

@anton_rh 2018-06-02 09:19:18

@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).

@Kaz 2018-06-03 04:35:17

@anton_rh Can you name three compilers in which it doesn't work?

@Christoph 2012-04-22 16:48:23

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)))

@Jonathan Leffler 2012-04-22 20:59:58

+1: You've done well retaining the type checking of typeof when available, and yet being correct when it isn't.

@pruzinat 2012-04-22 22:00:53

Are these brackets fine though? {}

@Christoph 2012-04-22 23:21:08

@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

@pruzinat 2012-04-23 10:33:02

@Christoph Thank you very much.

@Alexandros 2013-09-29 22:09:47

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.

@Christoph 2013-09-30 06:44:41

@Alexandros: [x] done

@Jonathan Leffler 2012-04-22 16:28:10

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.

@R.. 2012-04-22 16:59:25

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.

@pruzinat 2012-04-22 20:54:20

Sounds good to me, I think I'll merge both answers into my implementation snippet. Thank you.

@anton_rh 2018-05-31 04:28:39

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?

@Jonathan Leffler 2018-05-31 04:37:07

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 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 2018-05-31 04:40:09

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.

Related Questions

Sponsored Content

1 Answered Questions

[SOLVED] NULL substitute for a double in C language 99 standard

  • 2019-02-11 21:55:39
  • R00ki3 M1stak3
  • 74 View
  • 0 Score
  • 1 Answer
  • Tags:   c null c99

3 Answered Questions

[SOLVED] How does C type syntax avoid circular definition?

  • 2018-07-15 04:24:33
  • Senthil Kumaran
  • 88 View
  • 1 Score
  • 3 Answer
  • Tags:   c pointers

2 Answered Questions

[SOLVED] Aliasing struct and array the conformant way

1 Answered Questions

1 Answered Questions

[SOLVED] Using sizeof() on self-referential structs

  • 2017-04-08 17:33:30
  • Shrikant Giridhar
  • 43 View
  • -3 Score
  • 1 Answer
  • Tags:   c struct

1 Answered Questions

[SOLVED] Get offset to struct member without using offsetof

  • 2017-02-06 21:17:17
  • priti
  • 412 View
  • -1 Score
  • 1 Answer
  • Tags:   c pointers

2 Answered Questions

2 Answered Questions

[SOLVED] Are enums as bitfields implementation-defined types?

2 Answered Questions

[SOLVED] Can a "container_of" macro ever be strictly-conforming?

  • 2014-08-13 20:59:30
  • Drew McGowen
  • 650 View
  • 12 Score
  • 2 Answer
  • Tags:   c pointers standards

2 Answered Questions

[SOLVED] Const qualifier ignored

  • 2014-04-13 14:33:54
  • this
  • 450 View
  • 3 Score
  • 2 Answer
  • Tags:   c const typedef c99

Sponsored Content