Is pointer arithmetic within a struct well defined?

E

Eric Sosman

It does!



I know it seems weird, but it makes sense to me, because this is exactly
what you have to have be true for the optimizer to be able to make informed
decisions about whether a write to one thing can alter another thing.

Consider, given the above:
unsigned char *p1 = (unsigned char *) &fa;
unsigned char *p2 = (unsigned char *) &fa.x;
unsigned char *p3 = (unsigned char *) &fa.y;

It's *useful* to be assured that a write through p2 can't legitimately alter
the contents of p3. It's not a pointer into the whole struct, just into one
member of it. But you have to be able to indicate that sometimes you really
do mean you want to write through the pointer, thus p1 has to work.

One more pathetic bleat, and then I'll stop whimpering ...

If p2's only legitimate targets are limited to the bytes of
fa.x (and not all of fa), and if that limitation remains in effect
even when p2's value is converted to another type (void* in the
earlier example), then what are we to make of 6.7.2.1p15? It tells
us that a pointer to fa's first element can be converted to a pointer
to fa. But if a pointer derived from &fa.x can only be used to
access the constituent bytes of fa.x, is conversion to a struct
pointer useful? That is, does

((struct foo *) p2)->y = 27;

have undefined behavior?
 
S

Seebs

If p2's only legitimate targets are limited to the bytes of
fa.x (and not all of fa), and if that limitation remains in effect
even when p2's value is converted to another type (void* in the
earlier example), then what are we to make of 6.7.2.1p15? It tells
us that a pointer to fa's first element can be converted to a pointer
to fa. But if a pointer derived from &fa.x can only be used to
access the constituent bytes of fa.x, is conversion to a struct
pointer useful? That is, does
((struct foo *) p2)->y = 27;
have undefined behavior?

Heck if I know.

It may be that the intent is specifically that this really *does* give
you a pointer-to-the-whole-thing. In which case, it's interesting to
note that obtaining a pointer to a non-first member, converting to char *,
subtracting offsetof, and converting to a struct pointer, apparently does
*not* give you a pointer-to-the-whole-thing.

Which, I believe, breaks one of the popular list-node implementations
I've seen.

*thinks*

-s
 
T

Tim Rentsch

Seebs said:
Heck if I know.

I have been reconsidering this question and would now like to
offer a different perspective.

When dealing with ordinary members, pointer subtraction isn't
defined because there is no underlying array. That is, if we
have

struct { int fa; int fb; } foo;

then '&foo.fb - &foo.fa' is undefined behavior because there is
no array that contains both objects. The same is true for
different subarrays of a multi-dimensional arrays, eg, if we
have

int multi[11][13];

then 'multi[7] - multi[5]' is undefined behavior because there is
no (int) array that contains both (int) objects.

However, character types are different. (For simplicity let's
use 'char' to stand in for any of the three character types.)
Because of the special provisions for character access, in effect
every type T is "really" a union type T'

typedef union { T visible; char hidden[ sizeof (T) ]; } T';

If T is our earlier structure type, and we consider the casted
version of the pointer subtraction expression

(char *) &foo.fb - (char *) &foo.fa

now there /is/ an underlying array that contains both objects.
Seen from this perspective, the pointer subtraction here is now
defined behavior - or at least there is a reasonable argument
that the behavior is defined.

To be clear, I don't mean to say this interpretation is how the
Standard is intended to be read necessarily. But I think it fits
well with how developers (even expert developers) think about C,
because of the special rules for accessing using character types,
and because there is an explicit cast to (char *). Certainly
what is written in the Standard leaves some open questions in
this area, and it seems unlikely that the intention was to rule
out all intra-struct address calculations other than those based
on the address of the struct itself (or the first member).

It may be that the intent is specifically that this really *does*
give you a pointer-to-the-whole-thing. In which case, it's
interesting to note that obtaining a pointer to a non-first
member, converting to char *, subtracting offsetof, and converting
to a struct pointer, apparently does *not* give you a
pointer-to-the-whole-thing.

This question would be a good one to ask in a DR.
Which, I believe, breaks one of the popular list-node
implementations I've seen.

And that example provides great motivation for asking it.
 
S

Seebs

When dealing with ordinary members, pointer subtraction isn't
defined because there is no underlying array. That is, if we
have
struct { int fa; int fb; } foo;
then '&foo.fb - &foo.fa' is undefined behavior because there is
no array that contains both objects. The same is true for
different subarrays of a multi-dimensional arrays, eg, if we
have
int multi[11][13];
then 'multi[7] - multi[5]' is undefined behavior because there is
no (int) array that contains both (int) objects.

Oh, interesting. Because the actual arrays are subobjects of an array,
but their names decay into pointers to their first members, which are
not. *thinks*
To be clear, I don't mean to say this interpretation is how the
Standard is intended to be read necessarily. But I think it fits
well with how developers (even expert developers) think about C,
because of the special rules for accessing using character types,
and because there is an explicit cast to (char *). Certainly
what is written in the Standard leaves some open questions in
this area, and it seems unlikely that the intention was to rule
out all intra-struct address calculations other than those based
on the address of the struct itself (or the first member).

I suspect that's a case where it's very hard to ensure it's defined
without making promises we don't want.

-s
 

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

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top