Compile-time assertion to determine if pointer is an array - c

Currently, I have the following block of code to make safe string copying (it works):
#define STRCPY(dst, src) do { assert(((void*)(dst)) == ((void*) & (dst))); \
strlcpy(dst, src, sizeof(dst)); } while (0)
So it accepts the construction like:
const char *src = "hello";
char dest[5];
STRCPY(dest, src); //hell
And denies the following:
void (char *dst) {
STRCPY(dst, "heaven"); //unknown size of dst
}
The problem is that the block of code creates an assertion. Is there a way to perform this check on compilation time?
So I want to have the error on compilation (like creating an array with negative size) instead of crashing code if it possible.

If standard C is available, then you can do this:
#define STRCPY(dst, src) \
_Generic(&(dst), \
char(*)[sizeof(dst)]: strlcpy(dst,src,sizeof(dst)) )
Explanation:
You can't use a _Generic expression on an array type, because it is not one of the special cases that is exempt from the "array decay" rule (C17 6.3.2.1 §3). So by simply using _Generic((dst), ... in my example, dst would end up as a char* when the expression is evaluated, and then we would lose the information of its original type.
But if we take the address of the array using &, we do utilize one of those special cases and the array decay doesn't happen. Instead we end up with an array pointer, meaning that _Generic will have to check for an array pointer of the expected type and size: char(*)[sizeof(dst)].
As a side-note/safety concern, I never use do-while(0) macros and discourage them, but that's another story.

For compile-time assertion on whether or not dst is an array,
I would use #Lundin solution (or _Static_assert) in case C11 is available.
Else, I would use the following:
#define BUILD_BUG_ON_NON_ARRAY_TYPE(e) (sizeof(struct { int:-!!(((void*)(e)) != ((void*) & (e))); }))
Which basically takes your compile-time evaluated expression ((void*)(dst)) == ((void*) & (dst)) but instead of using it in run time assert just use it in a compile-time assertion manner.
So, overall I would change your macro into:
#define STRCPY(dst, src) do { BUILD_BUG_ON_NON_ARRAY_TYPE(dst); \
strlcpy(dst, src, sizeof(dst)); } while (0)

I've found that my post was closed as duplicated for a while and followed the link mentioned.
The solution is only for GCC, but it's fine for me, because I have night builds on GCC too. So the pre-draft of the code is:
#if defined(__GNUC__)
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
#define STRCPY(dst,src) do{__must_be_array(dst);strlcpy(dst, src, sizeof(dst));}while(0)
#else
#define STRCPY(dst,src) do{strlcpy(dst, src, sizeof(dst));}while(0)
#endif

If you want to check it the only way I can think of is:
assert((sizeof(dst)) != (sizeof(void*)));
but it only work if the size of the array is different than the size of the pointer on OPs system

Related

_Static_assert in unused generic selection

It looks like the typeof operator is likely to be accepted into the next C standard, and I was looking to see if there was a way to leverage this to create a macro using portable ISO-C that can get the length of an array passed into it or fail to compile if a pointer is passed into it. Normally generic selection can be used to force a compiler error when using an unwanted type by leaving it out of the generic association list, but in this case, we need a default association to deal with arrays of any length, so instead I am trying to force a compiler error for the generic association for the type we don't want. Here's an example of what the macro could look like:
#define ARRAY_SIZE(X) _Generic(&(X), \
typeof(&X[0]) *: sizeof(struct{_Static_assert(0, "Trying to get the array length of a pointer"); int _a;}), \
default: (sizeof(X) / sizeof(X[0])) \
)
The problem is that _Static_assert is tripping even when the generic association selected is the default association. For sake of simplicity, since the issue at hand is not related anything being introduced in C23, we'll make a test program that works explicitly to reject a pointer to int:
#include <stdio.h>
#include <stdlib.h>
#define ARRAY_SIZE(X) _Generic(&(X), \
int **: sizeof(struct{_Static_assert(0, "Trying to get the array length of a pointer"); int _a;}), \
default: (sizeof(X) / sizeof(X[0])) \
)
int main(void) {
int x[100] = {0};
int *y = x;
int (*z)[100] = {&x};
printf("length of x: %zu\n", ARRAY_SIZE(x));
printf("length of y: %zu\n", ARRAY_SIZE(y));
printf("length of z: %zu\n", ARRAY_SIZE(z));
printf("length of *z: %zu\n", ARRAY_SIZE(*z));
return EXIT_SUCCESS;
}
Building the above with -std=c11, I find _Static_assert tripping on all expansions of ARRAY_SIZE when I would expect to only have problems with the pointers that will use the int ** generic association.
According to 6.5.1.1 p3 of the C11 standard for Generic Selection,
None of the expressions from any other generic association of the generic selection is evaluated
Is this a bug in gcc and clang, or is there something I've missed in the standard that would cause the compile-time evaluation of this _Static_assert in the unused generic association?
It doesn't matter which generic selection is evaluated.
When the expression that is part of a _Status_assert has the value 0, this is considered a constraint violation and the compiler is required to generate a diagnostic.
You can't really mix _Static_assert with expressions that should return a value, such as a function-like macro. You could perhaps work around that with a "poor man's static assert", like one of the ugly tricks we used before C11:
#define POOR_STATIC_ASSERT(expr) (int[expr]){0}
#define CHECK(X) _Generic((&X), \
int **: 0,\
default: (sizeof(X) / sizeof(X[0])) \
)
#define ARRAY_SIZE(X) ( (void)POOR_STATIC_ASSERT(CHECK(X)), CHECK(X) )
Here the comma operator is called to have the macro CHECK return the size or zero, in case a type is valid or not. Then call the same macro again to have that one returned from the function-like macro ARRAY_SIZE. This will lead to some cryptic error from an ISO C compiler such as "error: ISO C forbids zero-size array".
The next problem is that &(X) in _Generic is by no means guaranteed to boil down to a int** so this macro isn't safe or reliable. Regarding array sizes though, there's a trick we can use. A pointer to an array of no size (incomplete type) is compatible with every array of the same element type no matter it's size. The macro could be rewritten as:
#define POOR_STATIC_ASSERT(expr) (int[expr]){0}
#define CHECK(X) _Generic((&X), \
int (*)[]: sizeof(X) / sizeof(X[0]), \
default: 0)
#define ARRAY_SIZE(X) ( (void)POOR_STATIC_ASSERT(CHECK(X)), CHECK(X) )
This will work for any int array no matter size but fail for everything else.
Utilizing some of the suggestions from Lundin's answer, I have come up with the following solution to the simplified problem:
#define STATIC_ASSERT_EXPRESSION(X, ERROR_MESSAGE) (sizeof(struct {_Static_assert((X), ERROR_MESSAGE); int _a;}))
#define NO_POINTERS(X) _Generic(&(X), \
int (*)[]: 1, \
default: 0 \
)
#define ARRAY_SIZE(X) ( (void)STATIC_ASSERT_EXPRESSION(NO_POINTERS(X), "Cannot retrieve the number of array elements from a pointer"), (sizeof(X) / sizeof(X[0])) )
For the actual use-case to be type generic using typeof, which should be coming to the C23 standard, replace the NO_POINTERS macro using this:
#define NO_POINTERS(X) _Generic(&(X), \
typeof(*X) (*)[]: 1, \
default: 0 \
)
By moving the _Static_assert outside of the Generic Selection, it will only be evaluated with the value that actually returns from the selection, so it won't get fired off for existing in an unused selection. Additionally, the number of elements calculation was also removed from the generic selection so that expression of the generic selection could safely be used in a Static Assert even if your array was a Variable-Length array which requires its size to be calculated at run-time.
The Static Assert itself is placed inside of an anonymous struct that we take the sizeof so that it is a part of an expression. And then as in Lundin's example, we use the comma operator to have that expression evaluated, and then thrown out and use the results of the array size calculation.
With this, we reject pointers while getting the number of elements in both static arrays and VLAs, plus we get a nice compiler error message when trying to pass in a pointer.

How to check if an array is of constant known size? [duplicate]

The standard array-size macro that is often taught is
#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))
or some equivalent formation. However, this kind of thing silently succeeds when a pointer is passed in, and gives results that can seem plausible at runtime until things mysteriously fall apart.
It's all-too-easy to make this mistake: a function that has a local array variable is refactored, moving a bit of array manipulation into a new function called with the array as a parameter.
So, the question is: is there a "sanitary" macro to detect misuse of the ARRAYSIZE macro in C, preferably at compile-time? In C++ we'd just use a template specialized for array arguments only; in C, it seems we'll need some way to distinguish arrays and pointers. (If I wanted to reject arrays, for instance, I'd just do e.g. (arr=arr, ...) because array assignment is illegal).
Linux kernel uses a nice implementation of ARRAY_SIZE to deal with this issue:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
with
#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
and
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
Of course this is portable only in GNU C as it makes use of two instrinsics:
typeof operator and __builtin_types_compatible_p function. Also it uses their "famous" BUILD_BUG_ON_ZERO macro which is only valid in GNU C.
Assuming a compile time evaluation requirement (which is what we want), I don't know any portable implementation of this macro.
A "semi-portable" implementation (and which would not cover all cases) is:
#define ARRAY_SIZE(arr) \
(sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr)))
with
#define IS_ARRAY(arr) ((void*)&(arr) == &(arr)[0])
#define STATIC_EXP(e) \
(0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))
With gcc this gives no warning if argument is an array in -std=c99 -Wall but -pedantic would gives a warning. The reason is IS_ARRAY expression is not an integer constant expression (cast to pointer types and subscript operator are not allowed in integer constant expressions) and the bit-field width in STATIC_EXP requires an integer constant expression.
This version of ARRAYSIZE() returns 0 when arr is a pointer and the size when its a pure array
#include <stdio.h>
#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0)
int main(void)
{
int a[5];
int *b = a;
int n = 10;
int c[n]; /* a VLA */
printf("%zu\n", ARRAYSIZE(a));
printf("%zu\n", ARRAYSIZE(b));
printf("%zu\n", ARRAYSIZE(c));
return 0;
}
Output:
5
0
10
As pointed out by Ben Jackson, you can force a run-time exception (dividing by 0)
#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0))
Sadly, you can't force a compile-time error (the address of arg must be compared at run-time)
With C11, we can differentiate arrays and pointers using _Generic, but I have only found a way to do it if you supply the element type:
#define ARRAY_SIZE(A, T) \
_Generic(&(A), \
T **: (void)0, \
default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0])))
int a[2];
printf("%zu\n", ARRAY_SIZE(a, int));
The macro checks: 1) pointer-to-A is not pointer-to-pointer. 2) pointer-to-elem is pointer-to-T. It evaluates to (void)0 and fails statically with pointers.
It's an imperfect answer, but maybe a reader can improve upon it and get rid of that type parameter!
Modification of bluss's answer using typeof instead of a type parameter:
#define ARRAY_SIZE(A) \
_Generic(&(A), \
typeof((A)[0]) **: (void)0, \
default: sizeof(A) / sizeof((A)[0]))
Here's one possible solution using a GNU extension called statement expressions:
#define ARRAYSIZE(arr) \
({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; \
sizeof(arr) / sizeof((arr)[0]);})
This uses a static assertion to assert that sizeof(arr) != sizeof(void*). This has an obvious limitation -- you can't use this macro on arrays whose size happens to be exactly one pointer (e.g. a 1-length array of pointers/integers, or maybe a 4-length array of bytes on a 32-bit platform). But those particular instances can be worked around easily enough.
This solution is not portable to platforms which don't support this GNU extension. In those cases, I'd recommend just using the standard macro and not worry about accidentally passing in pointers to the macro.
Here's another one which relies on the gcc typeof extension:
#define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; \
sizeof(arr) / sizeof(arr[0]);})
This works by attempting to set up an identical object and initializing it with an array designated initializer. If an array is passed, then the compiler is happy. If pointer is passed the compiler complains with:
arraysize.c: In function 'main':
arraysize.c:11: error: array index in non-array initializer
arraysize.c:11: error: (near initialization for 'p_is_a_pointer')
my personal favorite, tried gcc 4.6.3 and 4.9.2:
#define STR_(tokens) # tokens
#define ARRAY_SIZE(array) \
({ \
_Static_assert \
( \
! __builtin_types_compatible_p(typeof(array), typeof(& array[0])), \
"ARRAY_SIZE: " STR_(array) " [expanded from: " # array "] is not an array" \
); \
sizeof(array) / sizeof((array)[0]); \
})
/*
* example
*/
#define not_an_array ((char const *) "not an array")
int main () {
return ARRAY_SIZE(not_an_array);
}
compiler prints
x.c:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) \"not an array\") [expanded from: not_an_array] is not an array"
One more example to the collection.
#define LENGTHOF(X) ({ \
const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \
typeof(X[0]) (*should_be_an_array)[length] = &X; \
length; })
Pros:
It works with normal arrays, variable-length arrays, multidimensional
arrays, arrays of zero sized structs
It generates a compilation error (not warning) if you pass any pointer, struct or
union
It does not depend on any of C11's features
It gives you very readable error
Cons:
It depends on some of the gcc extensions: Typeof,
Statement Exprs, and (if you like it) Conditionals
It depends on C99 VLA feature
Awful, yes, but that works and it is portable.
#define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? \
(sizeof(arr)/sizeof(*arr)) : \
-1+0*fprintf(stderr, "\n\n** pointer in ARRAYSIZE at line %d !! **\n\n", __LINE__))
This will not detect anything at compile time but will print out an error message in stderr and return -1 if it is a pointer or if the array length is 1.
==> DEMO <==

Get pointer to struct by pointer to its member [duplicate]

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.

Why does qsort() not have a return value?

I'm a C++ programmer for the most part, but just for fun I was trying to do some generic programming in C. In particular, I implemented a generic sorting algorithm. The signature of my function is
int sort(void *data,
size_t num_elems,
size_t elem_size,
int (*cmp)(const void*, const void*))
When I compared this to qsort() in the standard library, I noticed that unlike my function, qsort() does not have a return value. Since sorting an array will always require swapping elements, the implementation requires a temporary storage of size elem_size. As C doesn't have templates, elem_size is not known at compile time, so the temporary storage must be allocated dynamically, which could fail. In that case, qsort() couldn't sort the array, but it couldn't report the error either, so there is no way of knowing if the array is sorted upon return.
Am I missing something here?
Any partitioning algorithm needs to be able to swap two elements, and the qsort API means that the code doesn't know how big they are at compile-time. But they don't need to be swapped as a whole; they can be swapped one byte at a time. (That's effectively what memcpy would do anyway.)
The following comment and macro are right at the beginning of qsort.c in the Gnu libc implementation. (Note that the code is subject to the LGPL)
/* Byte-wise swap two items of size SIZE. */
#define SWAP(a, b, size) \
do \
{ \
size_t __size = (size); \
char *__a = (a), *__b = (b); \
do \
{ \
char __tmp = *__a; \
*__a++ = *__b; \
*__b++ = __tmp; \
} while (--__size > 0); \
} while (0)
The function cannot fail -- well, unless the arguments are invalid in which case it's undefined behaviour and the function could not reliably detect that anyway.
qsort doesn't allocate any memory per se. (Of course it could do anything, but it's not allowed to fail due to memory allocation failure, so the implementor has to take that into account).

Kernel's "container_of" - any way to make it ISO conforming?

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.

Resources