#define MAKE(z, w) (int)(&(((z *)0)->w))
Can anyone fill me in?
Specifically, I'm confused on the ((z *)0) part. I'm not exactly sure
what that would give me.
T. I. A.
Looks like a horrible attempt to create a macro that does the same
thing as offsetof, except that this one invokes undefined behaviour.
Assuming that the parameter z in the macro is some type, most likely a
struct type, (z *)0 takes the number 0, and converts it to a pointer
to an object of type z. In other words, a null pointer of type z*.
Taken as a whole, the macro returns the byte offset of element 'w'
within an object of type 'z'. Why they chose to name it MAKE is
a mystery to me.
Expanding: ((z *)0) is zero, cast as a pointer to z. &(((z *)0)->w)
is the address of element 'w' within a 'z' object. Because this
particular 'z' object starts at zero, this pointer is also the
byte address of 'w'. The (int) cast causes the whole macro to return
an integer value of the byte address.
This macro makes some assumptions about the underlying CPU architecture.
If you look up offsetof() in Wikipedia, you'll find a somewhat more
portable definition of offsetof() along with a discussion about
the undefined behavior that christian.bau mentioned.
--
-Ed Falk, fa...@despams.r.us.com
http://thespamdiaries.blogspot.com/
The expression `(z *)0` is a null pointer of type `z*`. This isn't
quite the same as a "zero-valued" pointer (e.g., a null pointer may have
some arbitrary implementation-specific bit pattern that serves as a
trap), but the writer makes the assumption that null = zero.
Assuming for the moment that `(z *)0` gives you a zero-valued pointer,
the expression `((z *)0)->w` pretends there is a structure of type `z`
at address 0, and accesses the `w` field of the structure. The address
of this field is then taken, and the resulting address is cast to `int`.
E.g., given the structure definition
struct foo{
int32_t bar;
int32_t bas;
};
and plausible assumptions about structure layout and byte size,
`MAKE(foo foo, bas)` is intended to yield 4.
(Oh, and if differently-typed pointers have different representations on
your system, this will break horribly in very interesting ways.)
See also the `offsetof()` macro in <stddef.h>, which accomplishes this
same thing, but is defined by your compiler provider in a way which is
guaranteed to work on your machine.
By the way: Is there a portable way to get a zero-valued pointer? To
take a concrete example, assume a system where uintptr_t is defined, and
on which
union intptr {
void *p;
uintptr_t i;
};
/* ... */
union intptr u = {.p = 0};
assert(u.i == 0xDEADBEEF);
holds. On this system, is there an initializer for a pointer value for
which
union intptr v = {.p = ZERO_PTR};
assert(u.i == 0);
holds?
This should work:
#define ZERO_PTR ((intptr){.i = 0}).p
but is there a way to not require the union definition?
I'm wondering if `(void *)((uintptr_t)0)` might do the trick -- or is
the void* <-> uintptr_t conversion defined to map null to zero?
--Joel
Speaking of which, in:
#define fun_offsetof(type, member) \
(sizeof (char[(char *)&((type *)0)->member - (char *)0]))
struct s {
int x;
short y;
int z;
};
int main(void) {
return (int)fun_offsetof(struct s, z);
}
I wonder if that's any more portable...
Are you sure about "accesses the `w` field"? 6.3.2.1p2 suggests to me
that there is no access[3.1p1].
You're right, of course; an *actual* access would be a Bad Thing. I was
using the word "access" somewhat loosely. (Also, if I read this right,
offsetof() is supposed to work at compile-time.)
Good catch.
--Joel
[snip]
>
> By the way: Is there a portable way to get a zero-valued pointer? To
> take a concrete example, assume a system where uintptr_t is defined, and
> on which
> union intptr {
> void *p;
> uintptr_t i;
> };
> /* ... */
> union intptr u = {.p = 0};
> assert(u.i == 0xDEADBEEF);
> holds. On this system, is there an initializer for a pointer value for
> which
> union intptr v = {.p = ZERO_PTR};
> assert(u.i == 0);
> holds?
>
> This should work:
> #define ZERO_PTR ((intptr){.i = 0}).p
> but is there a way to not require the union definition?
How about this:
#define ZERO_POINTER ALL_ZEROES( void * )
#define ALL_ZEROES(T) \
( ( (union { unsigned char uc[sizeof(T)]; T it; }){{0}} ).it )
Agreed. :) I use that pattern, too, in C99.
> By the way: Is there a portable way to get a zero-valued pointer?
Assuming that by "zero-valued pointer", you mean a pointer all of whose
bits are zero,
#include <string.h>
void *zerop;
memset(&zerop, 0, sizeof zerop);
causes zerop to be a "zero-valued" (void *). I don't think you can
portably do anything with it, because it could then contain a trap
representation. Even using it as an initializer for other pointers
could be a problem.
--
Morris Keesan -- mke...@post.harvard.edu
Or perhaps the more ludicrous:
#define DUMMY_FUNC(type) \
((type * (*)(void))main)
#define NULLOF(type) (1 ? 0 : DUMMY_FUNC(type)())
#define fun_offsetof(type, member) \
(sizeof (char[ \
(char *)&(NULLOF(type)->member) - \
(char *)NULLOF(type) \
]))
struct s {
int x;
short y;
int z;
};
int main(void) {
return (int)fun_offsetof(struct s, z);
}
The "hope" being (for both examples) that 'sizeof' "helps" to avoid
evaluating the '->' operator's expression when its left operand is a
null pointer. The silly example above produces a null pointer without a
cast (for what that's worth, heh) and the ternary conditional operator
prevents 'main' from ever being called as the wrong function type. Just
for fun.
> I wonder if that's any more portable...
There is "non-portable", and there is "undefined behaviour".
If we have say a valid pointer char* p, and if T is an integer type
large enough so that ((T) p) converts p to an integer type without
losing any bits, then it is non-portable to assume that (T) (p + 1) is
one larger than (T) p. For example, in DOS with "huge" pointers, that
difference could be let's say "interesting" (p + 1 could be in a
different segment than p, so the result could be say 0x1234ffff and
0x123c0000). In a segmented architecture, casting to an integer type
could put the segment part in the lower bits, and the offset in the
higher bits. So this macro could produce completely meaningless
results.
In MacOS X with 64 bit code, pointers are 64 bit with the first 4 GB
of address space unused, and int is 32 bit. As a result, casting any
valid pointer other than a null pointer to int is _guaranteed_ to lose
vital information. Therefore, a compiler might decide that the result
of (int) p is _always_ zero, so that macro would always produce
zero.
Pointer difference is undefined behavior when one pointer is a null
pointer. But you could do
void* p = malloc (sizeof (type));
size_t offset = (char *)&((type *) p)->member - (char *) p;
free (p);
which will calculate offset correctly unless p == NULL. And an
optimising compiler might optimise malloc and free away and just
assign the correct constant to offset.
Well, your macro invokes undefined behaviour and therefore anything
can happen (I think it is C99 only). The original macro invokes
undefined behaviour as well, but can produce meaningless even ignoring
that, as part of the normal workings of the implementation.
Who's casting an integer to a pointer? Do you mean the code above? If
so, where? Do you mean the well-defined '(type *)0' in the first code?
I like to pretend that the representation of a pointer is
cryptographically signed and ought not to be tampered with.
>
> In MacOS X with 64 bit code, pointers are 64 bit with the first 4 GB
> of address space unused, and int is 32 bit. As a result, casting any
> valid pointer other than a null pointer to int is _guaranteed_ to lose
> vital information. Therefore, a compiler might decide that the result
> of (int) p is _always_ zero, so that macro would always produce
> zero.
>
Yeahbut who's doing that?
> Pointer difference is undefined behavior when one pointer is a null
> pointer. But you could do
>
> void* p = malloc (sizeof (type));
> size_t offset = (char *)&((type *) p)->member - (char *) p;
> free (p);
>
Even if the expression is not evaluated (in the code above)? Or are you
suggesting that the expression _is_ evaluated?
> which will calculate offset correctly unless p == NULL. And an
> optimising compiler might optimise malloc and free away and just
> assign the correct constant to offset.
>
That'd be great.
> Well, your macro invokes undefined behaviour and therefore anything
> can happen (I think it is C99 only). The original macro invokes
> undefined behaviour as well, but can produce meaningless even ignoring
> that, as part of the normal workings of the implementation.
Where does it invoke undefined behaviour, exactly? And no, it's
intended for C89. My news client might have mucked up indentation and
made it difficult to read. I apologize, but please do have a second
read of it.