Is it possible to implement the container_of macro in pure C90? I'm not sure how to do it as the Kernel implementation depends on GCC Hacks such as the typeof operator.
I'm asking because I would like to implement a generic container in C90 similar to the Kernel's linked list. The actual container I'm thinking of is a sequenced set similar to what you might get from Boost MultiIndex.
The use of typeof in the kernel definition of container_of() is just for compile-time type-checking - it ensures that the passed ptr is really a pointer to the same type as member. It can be modified to be entirely ANSI C at the cost of this type-checking:
#define container_of(ptr, type, member) ((type *)((char *)ptr - offsetof(type, member)))
(offsetof() is in <stddef.h>)
Yes.
The trick is replacing compound expression and typeof used for type checking. It can be done by replacing evaluation of (ptr) with:
(1 ? (ptr) : &((type*)0)->member)
A conditional operator always returns the first operand because its selection operand is 1. The other operand is not evaluated, therefore there is no UB due to NULL dereferencing. Moreover, the types of operands of ?: have to be compatible forcing type checking from a compiler.
The final macro is:
#define container_of(ptr, type, member) \
((type*)((char*)(1 ? (ptr) : &((type*)0)->member) - offsetof(type, member)))
This macro is even better than the original container_of.
The C89 variant can be used at file scope or for initialization of static variables, while the Linux one cannot.
Related
The following is the quick and easy way to define the containerof
macro, using only standard C features:
#include <stddef.h>
#define containerof(ptr, type, member) \
((type *)((char *)(ptr)-offsetof(type, member)))
Another commonly found definition of the macro uses GNU C statement
expressions to
provide additional type-checking:
#include <stddef.h>
#if __GNUC__+0 >= 3
#define containerof(ptr, type, member) ({ \
const __typeof__(((type *)0)->member) *containerof_memberptr_ = (ptr); \
(type *)((char *)(containerof_memberptr_)-offsetof(type, member)); })
#else
/* fallback */
#endif
My question is if there's a definition of the macro using only standard
C features that still checks the referenced type of the ptr argument
against the type of the member argument?
I've come up with the following definition:
#include <stddef.h>
#define containerof(ptr, type, member) \
((type *)((char *)(0 ? &((type *)0)->member : (ptr))-offsetof(type, member)))
Any incompatible pointer passed as the ptr argument violates the
following constraint of the conditional operator (C90 6.3.15
Conditional operator):
— both operands are pointers to qualified or unqualified versions of
compatible types
I was hoping for such a violation to generate an error by default,
however GCC and clang only issue a warning and there doesn't seem to be
a specific -Werror=... option in GCC to turn the emitted warning into
an error.
warning: pointer type mismatch in conditional expression
clang conveniently provides such an option called
-Werror=pointer-type-mismatch. And both GCC and clang support the
broad -pedantic-errors option that promotes the warning to an error as
well.
The standard requires a diagnostic message to be issued on constraint
violation (C90 5.1.1.3 Diagnostics), but that doesn't mean the
compiler has to error out on non-compliant code.
In conclusion, it is possible to define a containerof macro that
doesn't rely on extensions to the language, yet still yields some
type-checking behavior, whether that is by issuing a mandatory
diagnostic message or a compilation error, given compiler support.
One other advantage of this definition (shared with the one lacking
type-checking) over the one using a GNU statement expression is that it
results in a constant expression usable in initializer contexts.
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)?
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.
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)))
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.
Yes, you can make "container_of" macros to be strictly ISO C conforming. To do this you need two things:
take rid of GNU exensions;
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.
I've come across this expression
((void)(obj), (size_t)0)
What purpose does this expression (that looks like half a function signature) serve here?
It's part of a list of macros I found:
#define ers_alloc(obj,type) ((void)(obj), (type *)aMalloc(sizeof(type)))
#define ers_free(obj,entry) ((void)(obj), aFree(entry))
#define ers_entry_size(obj) ((void)(obj), (size_t)0)
#define ers_destroy(obj) ((void)(obj), (void)0)
#define ers_chunk_size(obj,size) ((void)(obj), (void)(size), (size_t)0)
These macros exist to allow the same code to compile in two configurations. If the controlling macro DISABLE_ERS is not defined, then ers_entry_size(some_o) will expand to this:
((some_o)->entry_size(some_o))
Everywhere ers_entry_size(some_o) is used in code. But the same code has to build successfully even when we define DISABLE_ERS. Those macros could thoretically be defined as
#define ers_alloc(obj,type) ((type *)aMalloc(sizeof(type)))
#define ers_free(obj,entry) (aFree(entry))
#define ers_entry_size(obj) ((size_t)0)
#define ers_destroy(obj) (void)0)
#define ers_chunk_size(obj,size) ((size_t)0)
But what if ers_entry_size(some_o) was the only way that a particular function used some_o? If the macro expands to ((size_t)0), now some_o becomes unused. Modern compilers emit warnings about unsed variables. We can't have our code compile to a bunch of warnings in a valid configuration of the library. That's bad library design.
So instead of the naive "no-op" definition, a smarter one is used. Since the comma operator evaluates each operand and discards all but the last, we can leverage it. When the macro is defined like you noted, ers_entry_size(some_o) will expand to
((void)(some_o), (size_t)0)
The first expression fed to the comma operator evaluates and discards some_o. It's being used now, so there is no warning. The second expression evaluates to the constant, and is the value of the whole parenthesized expression.
va_arg(a,type) macro expands to __builtin_va_arg(a,type) which is built-in part of compiler.
Is it possible to implement type based functionality, if yes how?
Notice that va_arg cannot determine the actual type of the
argument passed to the function, but uses whatever type is passed as
the type macro argument as its type.
Lets see how va_arg works, one possibility from here, is
#define va_arg(ap,t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
First of all, it's a MACRO not a function.
If you want write a type base MACRO as same as var_arg, you have two main tools, pointers and sizeof.
You can increase/decrease/lookup a pointer with sizeof steps.
With the preprocessor, you can indeed implement type based functionality, although it is limited. Don't forget that #define directives act with copy/paste, so you can not compare types in C standard, for instance(*).
However, you can reuse a type argument to build type-generic functions. Here is an example of a min generic function:
#define DEFINE_MIN(type) \
int min_##type(type a, type b) \
{ return a < b ? a : b; }
By the way, a gcc extension, typeof, gives the type of an expression (it doesn't rely on the programmer arguments anymore). Moreover, more recently, the C11 _Generic keyword permits you to write different treatment for expressions of different types.
(*) In the case of va_arg, the type argument is often use to get the size of type and type*.
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)?
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.
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)))
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.
Yes, you can make "container_of" macros to be strictly ISO C conforming. To do this you need two things:
take rid of GNU exensions;
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.