Using offsetof to create a pointer to the start of a struct?

N

Not Really Me

We have run into some code that is using a hand calculated offset to
dereference a pointer to a struct element to create a pointer to the start
of the struct.

(Example code below), basically a function is passed a pointer to a struct
member that is not the first member in the struct. The function actually
needs a pointer to the struct itself. To get that pointer, the code is
subtracting the hand calculated offset of the member, from the from the
pointer to the member, to create a pointer to the start of the struct. Ugly?

The problem we see is that c99 6.3.2.3p5 says that subtracting an int from a
pointer that is not pointing to an array is implementation dependent. In
theory this should be portable code.

To overcome this, someone suggested using the C99 offsetof macro to do get
the offset. Otherwise the math remains the same.

Is using offsetof still a violation of 6.3.2.3p5?

Is so, is there any safe way to do this (short of rewriting the code to pass
a pointer to the start of the struct)?

struct { int apples, int oranges, int lemons, int grapes, int limes } Fruit;

struct Fruit *pfruit;

foo( pfruit->grapes);

void foo( int *purple_fruit )
{
struct Fruit *local_fruit;
int apple;

local_fruit = &purple_fruit->grapes - offsetof( Fruit, grapes);
apple = local_fruit->apples;
}

Scott
 
P

Peter Nilsson

We have run into some code that is using a hand
calculated offset to dereference a pointer to a
struct element to create a pointer to the start
of the struct.

(Example code below), basically a function is passed
a pointer to a struct member that is not the first
member in the struct.  The function actually needs a
pointer to the struct itself.  To get that pointer,
the code is subtracting the hand calculated offset
of the member, from the from the pointer to the member,
to create a pointer to the start of the struct. Ugly?

Yes, offsetof is definitely better. But this sort of
thing can be dangerous and errors can be hard to debug.
The problem we see is that c99 6.3.2.3p5 says that
subtracting an int from a pointer that is not pointing
to an array is implementation dependent.

No, 6.3.2.3p5 talks about conversion of an integer to
a pointer.
 In theory this should be portable code.

Not quite.
To overcome this, someone suggested using the C99
offsetof macro to do get the offset.

It's available in C90 too.
 Otherwise the math remains the same.

Is using offsetof still a violation of 6.3.2.3p5?

No. Subtraction of an integer from a pointer does
not require the integer to be converted to a pointer.
Is so, is there any safe way to do this (short of
rewriting the code to pass a pointer to the start
of the struct)?

struct { int apples, int oranges, int lemons,
int grapes, int limes } Fruit;

struct Fruit *pfruit;

foo( pfruit->grapes);

void foo( int *purple_fruit )
{
    struct Fruit *local_fruit;
    int          apple;

    local_fruit = &purple_fruit->grapes -
offsetof( Fruit, grapes);

A few things...

This takes a pointer to the parameter, which is not what
you want.

The offsetof macro takes a _type_ as the first argument,
not an object.

Pointer subtraction is in units of the element being
pointed to, whereas offsetof returns a count in bytes.
[So you need a byte/character pointer to use it
effectively.]

size_t grapes_delta = offsetof(struct Fruit, grapes);
char *grapes_ptr = (char *) purple_fruit;
local_fruit = (struct Fruit *) (grapres_ptr - grapes_delta);

Or without temporaries...

local_fruit =
(struct Fruit *)
(
((char *) purple_fruit)
- offsetof(struct Fruit, grapes)
);

Strictly speaking, I think this violates the literal
interpretation of 6.5.6 (Additive operators), but I
can't see how it violates the intent, particularly
of offsetof.
    apple = local_fruit->apples;

}

Passing a pointer to struct Fruit may well be the better
option, not merely in terms of semantics, but also in
terms of design.
 
J

Jack Klein

We have run into some code that is using a hand calculated offset to
dereference a pointer to a struct element to create a pointer to the start
of the struct.

(Example code below), basically a function is passed a pointer to a struct
member that is not the first member in the struct. The function actually
needs a pointer to the struct itself. To get that pointer, the code is
subtracting the hand calculated offset of the member, from the from the
pointer to the member, to create a pointer to the start of the struct. Ugly?

The problem we see is that c99 6.3.2.3p5 says that subtracting an int from a
pointer that is not pointing to an array is implementation dependent. In
theory this should be portable code.

You just lost me here. In my C99, original and including TC3,
6.3.2.3, is Conversions, Other operands, Pointers, and paragraph 5
begins with the sentence "An integer may be converted to any pointer
type."

Perhaps you have mistyped the reference?

I'm looking at paragraph 8 of 6.5.6 Additive operators, which starts
with the phrase "When an expression that has integer type is added to
or subtracted from a pointer...".
To overcome this, someone suggested using the C99 offsetof macro to do get
the offset. Otherwise the math remains the same.

Is using offsetof still a violation of 6.3.2.3p5?

Again, I don't see that the paragraph you cite has any relevance at
all.
Is so, is there any safe way to do this (short of rewriting the code to pass
a pointer to the start of the struct)?

There is a safe and portable way to do this using offsetof, and this
is exactly the purpose of the macro, to allow a safe and standardized
way of doing things that you can't portably do without it.

But first you have to pull together some other things from other parts
of the standard.

6.2.6.1 p4 "Values stored in non-bit-field objects of any other object
type consist of n × CHAR_BIT bits, where n is the size of an object of
that type, in bytes."

6.5 p7 "An object shall have its stored value accessed only by an
lvalue expression that has one of the following types:

[snip all but last bullet item]

— a character type."

Essentially, any object can be treated as an array of bytes equal to
the size of the object.
struct { int apples, int oranges, int lemons, int grapes, int limes } Fruit;

struct Fruit *pfruit;

foo( pfruit->grapes);

void foo( int *purple_fruit )
{
struct Fruit *local_fruit;
int apple;

local_fruit = &purple_fruit->grapes - offsetof( Fruit, grapes);
apple = local_fruit->apples;
}

If you're going to post example code, instead of real code, you should
at least make sure that it is compilable, which the above is not due
to a large number of errors.

But the complete program below:

#include <stdio.h>
#include <stddef.h>

struct Fruit { int apples; int oranges; int lemons; int grapes; int
limes; };

void foo( int *purple_fruit )
{
struct Fruit *local_fruit;
local_fruit = (struct Fruit *)((char *)purple_fruit -
offsetof(struct Fruit, grapes));

printf("local_fruit = %p\n", local_fruit);
}

int main(void)
{
struct Fruit my_fruit;
struct Fruit *pfruit = &my_fruit;

printf("pfruit = %p\n", pfruit);
foo( &pfruit->grapes);
return 0;
}

....is perfectly conforming and portable.

By casting a pointer to any member of an aggregate object to a
character type, you now effectively have a pointer into an array of
character types the size of the aggregate object. Any pointer
additions or subtractions you do are perfectly valid as long as the
result lies within that array of bytes, that is the boundaries of the
original aggregate object, or to one past the last byte.

Since the offsetof macro returns an offset, in bytes, you can directly
subtract the value yielded from a pointer to char converted from the
address of the corresponding member, and yield the address of the
first byte of the structure type. And then you can convert, with a
cast, to a pointer to a structure type and it is guaranteed to point
to the full structure.

This is 100% portable to any conforming C90 or later implementation.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://c-faq.com/
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++
http://www.club.cc.cmu.edu/~ajo/docs/FAQ-acllc.html
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top