A commonly-used macro in the linux kernel (and other places) is container_of
, which is (basically) defined as follows:
#define container_of(ptr, type, member) (((type) *)((char *)(ptr) - offsetof((type), (member))))
Which basically allows recovery of a "parent" structure given a pointer to one of its members:
struct foo {
char ch;
int bar;
};
...
struct foo f = ...
int *ptr = &f.bar; // 'ptr' points to the 'bar' member of 'struct foo' inside 'f'
struct foo *g = container_of(ptr, struct foo, bar);
// now, 'g' should point to 'f', i.e. 'g == &f'
However, it's not entirely clear whether the subtraction contained within container_of
is considered undefined behavior.
On one hand, because bar
inside struct foo
is only a single integer, then only *ptr
should be valid (as well as ptr + 1
). Thus, the container_of
effectively produces an expression like ptr - sizeof(int)
, which is undefined behavior (even without dereferencing).
On the other hand, §6.3.2.3 p.7 of the C standard states that converting a pointer to a different type and back again shall produce the same pointer. Therefore, "moving" a pointer to the middle of a struct foo
object, then back to the beginning should produce the original pointer.
The main concern is the fact that implementations are allowed to check for out-of-bounds indexing at runtime. My interpretation of this and the aforementioned pointer equivalence requirement is that the bounds must be preserved across pointer casts (this includes pointer decay - otherwise, how could you use a pointer to iterate across an array?). Ergo, while ptr
may only be an int
pointer, and neither ptr - 1
nor *(ptr + 1)
are valid, ptr
should still have some notion of being in the middle of a structure, so that (char *)ptr - offsetof(struct foo, bar)
is valid (even if the pointer is equal to ptr - 1
in practice).
Finally, I came across the fact that if you have something like:
int arr[5][5] = ...
int *p = &arr[0][0] + 5;
int *q = &arr[1][0];
while it's undefined behavior to dereference p
, the pointer by itself is valid, and required to compare equal to q
(see this question). This means that p
and q
compare the same, but can be different in some implementation-defined manner (such that only q
can be dereferenced). This could mean that given the following:
// assume same 'struct foo' and 'f' declarations
char *p = (char *)&f.bar;
char *q = (char *)&f + offsetof(struct foo, bar);
p
and q
compare the same, but could have different boundaries associated with them, as the casts to (char *)
come from pointers to incompatible types.
To sum it all up, the C standard isn't entirely clear about this type of behavior, and attempting to apply other parts of the standard (or, at least my interpretations of them) leads to conflicts. So, is it possible to define container_of
in a strictly-conforming manner? If so, is the above definition correct?
This was discussed here after comments on my answer to this question.
See Question&Answers more detail:
os