The offsetof macro is an ANSI C library feature defined in <stddef.h> that evaluates to the offset in bytes of a specified member within a struct or union type, returned as a value of type size_t. It takes two parameters: the structure or union type and the name of the member (which cannot be a bit-field), allowing developers to calculate the displacement of a field from the beginning of the object, including any padding inserted by the compiler.
Commonly used for implementing generic data structures and serialization, offsetof is essential in C for patterns like container_of, which retrieves a pointer to an enclosing structure from a pointer to one of its members. A typical implementation relies on pointer arithmetic with a null pointer:
#define offsetof(st, m) ((size_t)&(((st*)0)->m))While this works on many compilers, the C standard defines it as an integral constant expression, and modern compilers like GCC often use built-in extensions like __builtin_offsetof for better safety and diagnostics.
Key Characteristics and Limitations
Usage: It calculates the byte offset of a member, which is not necessarily the sum of previous member sizes due to alignment and padding.
C++ Constraints: In C++,
offsetofis restricted to POD types (pre-C++11), standard-layout types (C++11 and later), or conditionally-supported cases (C++17); using it with virtual inheritance or non-standard-layout classes results in undefined behavior.Restrictions: The macro cannot be applied to bit-fields, static data members, or member functions, and the member must be a subobject of the type.
Portability: Although the traditional null-pointer dereference method is widely used, it can be considered undefined behavior by the strictest interpretations of the C standard, leading some implementations to use compiler-specific intrinsics.
Videos
R.. is correct in his answer to the second part of your question: this code is not advised when using a modern C compiler.
But to answer the first part of your question, what this is actually doing is:
(
(int)( // 4.
&( ( // 3.
(a*)(0) // 1.
)->b ) // 2.
)
)
Working from the inside out, this is ...
- Casting the value zero to the struct pointer type
a* - Getting the struct field b of this (illegally placed) struct object
- Getting the address of this
bfield - Casting the address to an
int
Conceptually this is placing a struct object at memory address zero and then finding out at what the address of a particular field is. This could allow you to figure out the offsets in memory of each field in a struct so you could write your own serializers and deserializers to convert structs to and from byte arrays.
Of course if you would actually dereference a zero pointer your program would crash, but actually everything happens in the compiler and no actual zero pointer is dereferenced at runtime.
In most of the original systems that C ran on the size of an int was 32 bits and was the same as a pointer, so this actually worked.
It has no advantages and should not be used, since it invokes undefined behavior (and uses the wrong type - int instead of size_t).
The C standard defines an offsetof macro in stddef.h which actually works, for cases where you need the offset of an element in a structure, such as:
#include <stddef.h>
struct foo {
int a;
int b;
char *c;
};
struct struct_desc {
const char *name;
int type;
size_t off;
};
static const struct struct_desc foo_desc[] = {
{ "a", INT, offsetof(struct foo, a) },
{ "b", INT, offsetof(struct foo, b) },
{ "c", CHARPTR, offsetof(struct foo, c) },
};
which would let you programmatically fill the fields of a struct foo by name, e.g. when reading a JSON file.