The container_of
and its WinApi equivalent CONTAINING_RECORD
are popular and useful macros. In principle, they use pointer arithmetic over char*
to recover a pointer to an aggregate to which a given pointer to the member belongs.
The minimalistic implementation is usually:
#define container_of(ptr, type, member) \
(type*)((char*)(ptr) - offsetof(type, member))
However, the strict compliance of a usage pattern of this macro is debatable. For example:
struct S {
int a;
int b;
};
int foo(void) {
struct S s = { .a = 42 };
int *p = &s.b;
struct S *q = container_of(p, struct S, b);
return q->a;
}
To my understanding, the program is not strictly compliant because:
- expression
s.b
is an l-value of typeint
&s.b
is a pointer. Its value may carry implementation defined attributes like a size of a value it is pointing to(char*)&s.b
does not do anything special to the potential metadata bound to the value of the pointer(char*)&s.b - offsetof(struct S, b)
, here UB is invoked because of pointer arithmetic outside of the value that the pointer is pointing to
I've noticed that the problem is not the container_of
macro itself. It is rather the way how ptr
argument is constructed.
If the pointer was computed from the l-value of struct S
type
then there would be no out-of-bounds arithmetic. There would be no UB.
A potentially compliant version of the program would be:
int foo(void) {
struct S s = { .a = 42 };
int *p = (int*)((char*)&s + offsetof(struct S, b));
struct S *q = container_of(p, struct S, b);
return q->a;
}
The actual arithmetic taking place is:
container_of(ptr, struct S, b)
Expand container_of
(struct S*)((char*)(ptr) - offsetof(struct S, b))
Place expression for ptr
(struct S*)((char*)((int*)((char*)&s + offsetof(struct S, b))) - offsetof(struct S, b))
Drop casts (char*)(int*)
(struct S*)((char*)&s + offsetof(struct S, b) - offsetof(struct S, b)))
Adding offsetof(struct S,b)
does not overflow struct S
. There is no UB when doing arithmetics.
The positive and negative terms are reduced.
(struct S*)((char*)&s)
Now drop redundant casts.
&s
The question.
Is the above derivation correct?
Is such a usage of container_of
strictly compliant?
If so, then the computation of a pointer to the member could be delegated to a new macro named member_of
.
The pointer can be constructed in a similar fashion as container_of
.
This new macro would be a complement of container_of
to be used in strictly compliant programs.
#define member_of(ptr, type, member) \
(void*)((char*)(ptr) + offsetof(type, member))
or a bit more convenient and typesafe but less portable (though fine in C23) version:
#define member_of(ptr, member) \
(typeof(&(ptr)->member))((char*)(ptr) + offsetof(typeof(*(ptr)), member))
The program would be:
int foo(void) {
struct S s = { .a = 42 };
int *p = member_of(&s, struct S, b);
struct S *q = container_of(p, struct S, b);
return q->a;
}