0

Possible Duplicates:
Does the 'offsetof' macro from <stddef.h> invoke undefined behaviour?
dereferencing the null pointer

    #define _OFFS_OF_MEMBER(p_type, p_member) (size_t)&(((p_type *)NULL)->p_member)

    struct a 
    {
             int a, b;
    };

    size_t l = _OFFS_OF_MEMBER(struct a, b);

I had a little chat/conversation with some fellow users, and one of them said that this is dereferencing and accessing the address space near address NULL. I said: taking an address of a member will not access, touch, or read the value of that member. According to standard it is completely safe.

    struct a* p = NULL;
    size_t offset = &p->b; // this may NOT touch b, it is not dereferencing
    // p->b = 0; // now, we are dereferincing: acccess violation time!

Is this always a safe way to calculate offset, or are compilers free to dereference and mess up the memory near address NULL according to standards?

I know there is a safe way to calculate offsets provided by the standard, but I am curious what you have to say about this. All in favor of my explenation: up-vote this question :-)

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
bert-jan
  • 958
  • 4
  • 15
  • 2
    If you're going to say "According to standard it is completely safe." why not provide a standard reference for your assertion? – Mark B Jul 13 '11 at 18:03
  • 2
    This may interfere badly with multiple inheritance. – Alexandre C. Jul 13 '11 at 18:03
  • I am open to the idea it is not safe, but I want to find out your oppionions – bert-jan Jul 13 '11 at 18:04
  • @Alexandre C, no virtual base classes assumed! You're right – bert-jan Jul 13 '11 at 18:05
  • This is essentially a duplicate of http://stackoverflow.com/q/2896689/293791 – Dennis Zickefoose Jul 13 '11 at 18:14
  • @bert-jan: no need for virtual base classes. Just multiple inheritance plays badly with C casts. – Alexandre C. Jul 13 '11 at 18:25
  • @Dennis: No, this is not a duplicate of that question at all. The "pointer to member" is totally different than "pointer to object"... – Nemo Jul 13 '11 at 18:44
  • @Nemo: He doesn't have a pointer to member, he has a pointer to object. Which he has to dereference in order to access that object's member. – Dennis Zickefoose Jul 13 '11 at 18:53
  • I was only thinking of simple structures. Multiple inheritance and virtual base classes may need an object to calculate the address of members. And the difference (the offset) may not be constant at compile-time. In fact it may very from time to time at runtime. – bert-jan Jul 13 '11 at 18:54
  • @Dennis: My point is that `p = 0; q = &*p;` is _totally_ different from `p = 0; q = &p->foo;`. I believe the former is legal C99 (and is what the "duplicate" question asks) while the latter is Undefined Behavior (and is what _this_ question asks). So not only is this question not a duplicate, the answer is different. (At least, nothing quoted so far at this question or the other serves to answer it.) This question should be re-opened because it is _not_ a duplicate. – Nemo Jul 13 '11 at 18:58
  • enum {offset1 = _OFFS_OF_MEMBER(struct a, b), }; // the macro is used in a place where a constant is expected, and the compiler does not complain. It should if the p_type parameter is a class with virtual base class, or more than one base classes. If the compiler doesn't complain, it is a compile-time constant. – bert-jan Jul 13 '11 at 19:02
  • @bert-jan: Whether the compiler complains is irrelevant. What is relevant is what the spec says. And now we won't know the answer because the question was incorrectly closed as a duplicate. – Nemo Jul 13 '11 at 20:34
  • The true problem is with these virtual base classes and multiple inheritance, there is no way of calculating the offset at compile-time. Still I thing my question was useful. Thanks for the feedback! Lets leave it for now. – bert-jan Jul 13 '11 at 21:02

5 Answers5

2

Absolutely not. To even create a pointer by adding an offset to NULL is to invoke Undefined Behavior. Someone more motivated can dig up chapter and verse from the spec.

By the way, whatever your reason is for wanting to compute these offsets, it is a probably a bad one.

Nemo
  • 70,042
  • 10
  • 116
  • 153
  • ISO/IEC 14882:2003, 5.2.5: "3. If E1 has the type “pointer to class X,” then the expression `E1->E2` is converted to the equivalent form `(*(E1)).E2` (...)". Standards have changed on this point. For instance, OP's macro is valic C99 and probably valid C++0x. – Alexandre C. Jul 13 '11 at 18:12
  • @Alexandre: Can you cite the relevant C99 section? I believe the only pointers you are even allowed to _compute_ (not dereference; just compute) are the return from malloc(), pointer into an array (or one past the end) and NULL itself. – Nemo Jul 13 '11 at 18:25
  • §6.5.3.2/3, according to [this answer](http://stackoverflow.com/questions/2896689/dereferencing-the-null-pointer/2896975#2896975). "If the operand [of the unary & operator] is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue." – Alexandre C. Jul 13 '11 at 18:33
  • @Alexandre: But that is not the same as taking a pointer to a member... I am 99.9% certain this macro is invalid even in C99. (Also for this reason this question is NOT a duplicate.) – Nemo Jul 13 '11 at 18:43
2

You're not dereferencing anything invalid here. All that macro does is tell the compiler that a structure of type p_type exists in memory at the address NULL. It then takes the address of p_member, which is a member of this fictitious structure. So, no dereferencing anywhere.

In fact, this is exactly what the offsetof macro, defined in stddef.h does.

EDIT:
As some of the comments say, this may not work well with C++ and inheritance, I've only used offsetof with POD structures in C.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
1

It is invalid C++.

From ISO/IEC 14882:2003, 5.2.5:

3/ If E1 has the type “pointer to class X,” then the expression E1->E2 is converted to the equivalent form (*(E1)).E2 (...)

However, there has been a defect report about this, and it is valid C99 (and probably valid C++0x too):

From ISO/IEC 9899:1999, 6.5.3:

2/ If the operand [of the unary & operator] is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
0

So &p->b is &(p->b) is (by definition) &((*p).b), which does seem to involve a dereference of p before getting the member. It may work on most compilers though even if it violates the standard. As noted in a comment this work probably work right in cases involving multiple inheritance.

What problem are you trying to solve by getting this offset? Could you use references, pointers, or pointers-to-member instead?

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • I am trying to get a data driven impementation for storing an object in a document. If an object tells the various offsets of it's sub-objects, (sub-objects and the object itself share a common base class) then I can streamline the storage of a lot of classes without having to add virtual functions to all those classes. – bert-jan Jul 13 '11 at 18:15
0
#define _OFFS_OF_MEMBER(p_type, p_member) (size_t)&(((p_type *)NULL)->p_member)

struct a 
{
         int a, b;
};

size_t l = _OFFS_OF_MEMBER(struct a, b);

Is the same as (after preprocessing)

struct a { int a, b; };
size_t l = (size_t)&(((struct a *)NULL)->b);

I see that you are casting NULL to a pointer-to-struct a, and then getting the address of its member b.

As far as I know, since you are getting the address of b, and not actually accessing or modifying (dereferencing) the value of b, the compiler will not complain, and you will not get a runtime error. Since NULL (or 0) is the starting address of a, this will give you the offset. This is actually a pretty nifty way to do that.

Andrew Rasmussen
  • 14,912
  • 10
  • 45
  • 81