Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

C99 clarification on pointers to aggregate object and its sub objects

49 views
Skip to first unread message

ty ty

unread,
Jan 10, 2012, 1:59:04 AM1/10/12
to
I have seen lengthy and inconclusive arguments on the use of pointers
to an aggregate object and its sub objects, so I would like to clarify
it here. The relevant sections of C99 are these:

6.3.2.3/7: When a pointer to an object is converted to a pointer to a
character type,
the result points to the lowest addressed byte of the object.
Successive increments of the
result, up to the size of the object, yield pointers to the remaining
bytes of the object.

6.5.3.2/3: The unary & operator yields the address of its operand. If
the operand has type ‘‘type’’,
the result has type ‘‘pointer to type’’

6.5.6/8: When an expression that has integer type is added to or
subtracted from a pointer, ..., If both the pointer operand and the
result point to elements of the same array object, or one past the
last element of the array object, the evaluation shall not produce an
overflow; otherwise, the
behavior is undefined.

6.5.6/9: When two pointers are subtracted, both shall point to
elements of the same array object,
or one past the last element of the array object

6.5.8/5: When two pointers are compared, the result depends on the
relative locations in the
address space of the objects pointed to. If two pointers to object
types both point to the
same object, or both point one past the last element of the same array
object, they
compare equal. If the objects pointed to are members of the same
aggregate object,
pointers to structure members declared later compare greater than
pointers to members
declared earlier in the structure, and pointers to array elements with
larger subscript
values compare greater than pointers to elements of the same array
with lower subscript
values. All pointers to members of the same union object compare
equal. If the
expression P points to an element of an array object and the
expression Q points to the
last element of the same array object, the pointer expression Q+1
compares greater than
P. In all other cases, the behavior is undefined.

6.5.9/6: Two pointers compare equal if and only if both are null
pointers, both are pointers to the
same object (including a pointer to an object and a subobject at its
beginning) or function,
both are pointers to one past the last element of the same array
object, or one is a pointer
to one past the end of one array object and the other is a pointer to
the start of a different
array object that happens to immediately follow the first array object
in the address
space.

Given that sizeof(int) == 4, and the declaration:
int a[3][4];
int *p = & a[0][0];
int (*q)[4] = &a [0];
int (*r)[3][4] = &a;

The questions are:
a) Is the conditional expression (p == r) always true? In others
words, is 6.5.9/6 transitive ?

b) Is (char *)p:
i) a pointer to the first byte of the integer object a[0][0] or
ii) a pointer to the first byte of the 1 dimensional array a [0] or
iii) a pointer to the first byte of the 2 dimensional array a or
iv) all of the above ?

c) Following (b), do any of these lead to undefined behaviour?
i) memset ((char*)p, 0, sizeof (*p));
ii) memset ((char*)p, 0, sizeof (*q));
iii) memset ((char*)p, 0, sizeof (*r));

d) Since an array in an expression is converted to a pointer to its
1st element, and &a[0][0] points to an object of integer size, are
these really valid:
i) memset (a, 0, sizeof (a));
ii) memset (&a[0][0], 0, sizeof (a));
or we can only do this:
iii) memset (&a, 0, sizeof (a));

e) If the pointer is not to the first element, is there any
difference? Is there undefined behaviour in this:
i) memset (&a[1][0], 0, sizeof (a) - sizeof (a[0]));
ii) memset (&a[1], 0, sizeof (a) - sizeof (a[0]));

f) When are 2 pointers, one derived from an object and another from
the subobject considered to 'point to the same object'. Do any of
these expressions lead to undefined behaviour?
i) (char *)&a[0][0] < (char *)a + sizeof (a)
ii) (char *)&a[0][1] < (char *)a + sizeof (a)
iii) (char *)&a[0][1] + 1 < (char *)&a[2][0]

James Kuyper

unread,
Jan 10, 2012, 7:27:11 AM1/10/12
to
That conditional expression compares pointers to incompatible types,
neither of which is void, and as such violates a constraint (6.5.9p2). I
believe that what 6.5.9p6 was meant to refer to in the clause about
subobjects was a comparison after suitable conversions, such as &a[0][0]
== (int*) &a[0], but it doesn't make that clear.

I've frequently claimed that the standard fails to define where the
pointer resulting from such a pointer conversion points at (except for
arrays of char, in which case 6.3.2.3p7 provides the definition). The
only thing it does guarantee is that the result of the conversion, if
converted back to the original type, will compare equal with the
original pointer. This assertion has probably been part of some of the
"lengthy and inconclusive arguments" you refer to.

I have vague memories of being given a citation that does define where
the result of such a conversion points, but I can never find it when I'm
looking for it. I can easily find such such wording for structures
(6.7.2.1p13) and unions (p14); by analogy, the wording for arrays should
be somewhere under 6.7.5.2. If 6.5.9p6 were clear about referring to
suitably converted pointers, it would provide that definition. In any
event, I'm quite certain that the intent was that &a[0][0] == (int*)&a,
whether or not the standard clearly says so, and all of my other answers
to your questions will be based upon assuming that the standard does
clearly say so.

The standard provides no definition for the term "subobject". I believe
that the intended meaning is recursive, which would mean that *p is a
subobject of both *q and *r. That would ensure that equality
comparisons between such pointers are transitive.

> b) Is (char *)p:
> i) a pointer to the first byte of the integer object a[0][0] or
> ii) a pointer to the first byte of the 1 dimensional array a [0] or
> iii) a pointer to the first byte of the 2 dimensional array a or
> iv) all of the above ?

iv.

> c) Following (b), do any of these lead to undefined behaviour?
> i) memset ((char*)p, 0, sizeof (*p));
> ii) memset ((char*)p, 0, sizeof (*q));
> iii) memset ((char*)p, 0, sizeof (*r));

memset()'s first parameter is of type void*, rendering the casts
unnecessary. With or without the casts, none of those function calls has
undefined behavior.

> d) Since an array in an expression is converted to a pointer to its
> 1st element, and &a[0][0] points to an object of integer size, are
> these really valid:
> i) memset (a, 0, sizeof (a));
> ii) memset (&a[0][0], 0, sizeof (a));
> or we can only do this:
> iii) memset (&a, 0, sizeof (a));

All three have the same well-defined behavior, which makes 'i' the
preferred form.

> e) If the pointer is not to the first element, is there any
> difference? Is there undefined behaviour in this:
> i) memset (&a[1][0], 0, sizeof (a) - sizeof (a[0]));
> ii) memset (&a[1], 0, sizeof (a) - sizeof (a[0]));

No.

> f) When are 2 pointers, one derived from an object and another from
> the subobject considered to 'point to the same object'. Do any of
> these expressions lead to undefined behaviour?
> i) (char *)&a[0][0] < (char *)a + sizeof (a)
> ii) (char *)&a[0][1] < (char *)a + sizeof (a)
> iii) (char *)&a[0][1] + 1 < (char *)&a[2][0]

No.
--
James Kuyper

Wojtek Lerch

unread,
Jan 10, 2012, 11:29:18 AM1/10/12
to
On 10/01/2012 7:27 AM, James Kuyper wrote:
> On 01/10/2012 01:59 AM, ty ty wrote:
>> Given that sizeof(int) == 4, and the declaration:
>> int a[3][4];
>> int *p =& a[0][0];
>> int (*q)[4] =&a [0];
>> int (*r)[3][4] =&a;
...

>> b) Is (char *)p:
>> i) a pointer to the first byte of the integer object a[0][0] or
>> ii) a pointer to the first byte of the 1 dimensional array a [0] or
>> iii) a pointer to the first byte of the 2 dimensional array a or
>> iv) all of the above ?
>
> iv.

Right; they're all the same byte. The tricky question is how many times
you can increment the pointer before the behaviour becomes undefined.

If you take the standard's words literally, p points to an object of
type int. The size of that object is four. Therefore adding five to
(char*)p is undefined behaviour.

>> c) Following (b), do any of these lead to undefined behaviour?
>> i) memset ((char*)p, 0, sizeof (*p));
>> ii) memset ((char*)p, 0, sizeof (*q));
>> iii) memset ((char*)p, 0, sizeof (*r));
>
> memset()'s first parameter is of type void*, rendering the casts
> unnecessary. With or without the casts, none of those function calls has
> undefined behavior.

I don't think that's entirely clear. If adding five to (char*)p is
undefined behaviour, then ii and iii are both undefined, aren't they?

>> d) Since an array in an expression is converted to a pointer to its
>> 1st element, and&a[0][0] points to an object of integer size, are
>> these really valid:
>> i) memset (a, 0, sizeof (a));
>> ii) memset (&a[0][0], 0, sizeof (a));
>> or we can only do this:
>> iii) memset (&a, 0, sizeof (a));
>
> All three have the same well-defined behavior, which makes 'i' the
> preferred form.

Again, I don't think that's entirely clear from the words of the
standard, even though it probably is the intent.

James Kuyper

unread,
Jan 10, 2012, 12:22:34 PM1/10/12
to
On 01/10/2012 11:29 AM, Wojtek Lerch wrote:
> On 10/01/2012 7:27 AM, James Kuyper wrote:
>> On 01/10/2012 01:59 AM, ty ty wrote:
>>> Given that sizeof(int) == 4, and the declaration:
>>> int a[3][4];
>>> int *p =& a[0][0];
>>> int (*q)[4] =&a [0];
>>> int (*r)[3][4] =&a;
> ...
>
>>> b) Is (char *)p:
> >> i) a pointer to the first byte of the integer object a[0][0] or
> >> ii) a pointer to the first byte of the 1 dimensional array a [0] or
> >> iii) a pointer to the first byte of the 2 dimensional array a or
> >> iv) all of the above ?
> >
> > iv.
>
> Right; they're all the same byte. The tricky question is how many times
> you can increment the pointer before the behaviour becomes undefined.
>
> If you take the standard's words literally, p points to an object of
> type int. The size of that object is four. Therefore adding five to
> (char*)p is undefined behaviour.

More accurately, that object has the size sizeof(int), which could be
any positive integer, though 4 is one of most popular options nowadays.

Pointers to character type have a special exemption from those rules,
though it's tricky to prove that.. p points at a, a[0], and a[0][0]. All
of those are objects, and according to 6.2.6.1p4, any of those objects
can be copied into an array of unsigned char with a length equal to the
size of the object (for example, by using memcpy()). The resulting set
of bytes constitutes the object representation of that value. 6.5p7 says
that an object with a given type and value can be copied, either using
memcpy or as an array of character type, into memory with no declared
type, and as a result produce an object whose effective type and value
are the same as the source object.

The fact that, in both clauses, the standard gives memcpy() as an
example of how this can be done, rather than specifying that memcpy() is
the only way to do it, implies that this is not a magic feature of
memcpy(). A user-defined function with the same interface as memcpy(),
which implements the same behavior defined for memcpy(), but which has a
different name, could be used as well. That, in turn, implies that
entirety of any object's representation may be accessed by using through
a pointer to a character type

That's a very round-about way of reaching such a conclusion; I'd prefer
it if the standard said these things more directly. I consider such an
argument acceptable only because it confirms what we all "know" to be
true, despite the fact that the standard fails to say it more clearly.

>>> c) Following (b), do any of these lead to undefined behaviour?
>>> i) memset ((char*)p, 0, sizeof (*p));
>>> ii) memset ((char*)p, 0, sizeof (*q));
>>> iii) memset ((char*)p, 0, sizeof (*r));
>>
>> memset()'s first parameter is of type void*, rendering the casts
>> unnecessary. With or without the casts, none of those function calls has
>> undefined behavior.
>
> I don't think that's entirely clear. If adding five to (char*)p is
> undefined behaviour, then ii and iii are both undefined, aren't they?

Disagreeing with you about the undefined behavior of
1+sizeof(int)+(char*)p necessarily implies that I disagree with you
about ii and iii.

>>> d) Since an array in an expression is converted to a pointer to its
>>> 1st element, and&a[0][0] points to an object of integer size, are
>>> these really valid:
>>> i) memset (a, 0, sizeof (a));
>>> ii) memset (&a[0][0], 0, sizeof (a));
>>> or we can only do this:
>>> iii) memset (&a, 0, sizeof (a));
>>
>> All three have the same well-defined behavior, which makes 'i' the
>> preferred form.
>
> Again, I don't think that's entirely clear from the words of the
> standard, even though it probably is the intent.

All of your concerns would be more relevant if char* were replace with
int*, and memset() were replaced with a function like intset(), defined
below:

void intset(int *s, int c, size_t n)
{
while(n--)
*s++ = c;
}

intset(a[0], 0, 5) would indeed have undefined behavior (the 5 here is
one more than the length of the second dimension of 'a'; it has nothing
to do with the 5 that you were mentioning earlier).

Wojtek Lerch

unread,
Jan 10, 2012, 1:28:45 PM1/10/12
to
On 10/01/2012 12:22 PM, James Kuyper wrote:
> On 01/10/2012 11:29 AM, Wojtek Lerch wrote:
>> On 10/01/2012 7:27 AM, James Kuyper wrote:
>>> On 01/10/2012 01:59 AM, ty ty wrote:
>>>> Given that sizeof(int) == 4, and the declaration:
>>>> int a[3][4];
>>>> int *p =& a[0][0];
>>>> int (*q)[4] =&a [0];
>>>> int (*r)[3][4] =&a;
>> ...
>>
>>>> b) Is (char *)p:
>>>> i) a pointer to the first byte of the integer object a[0][0] or
>>>> ii) a pointer to the first byte of the 1 dimensional array a [0] or
>>>> iii) a pointer to the first byte of the 2 dimensional array a or
>>>> iv) all of the above ?
>>>
>>> iv.
>>
>> Right; they're all the same byte. The tricky question is how many times
>> you can increment the pointer before the behaviour becomes undefined.
>>
>> If you take the standard's words literally, p points to an object of
>> type int. The size of that object is four. Therefore adding five to
>> (char*)p is undefined behaviour.
>
> More accurately, that object has the size sizeof(int), which could be
> any positive integer, though 4 is one of most popular options nowadays.

Yeah, 4 was the OP's assumption (see the firs line I quoted above).

> Pointers to character type have a special exemption from those rules,
> though it's tricky to prove that.. p points at a, a[0], and a[0][0]. All
> of those are objects,

No, p points to an object whose type is int. It's the first element of
the array a[0], not the entire array. The size of the object that p
points to is sizeof(int), not sizeof(a[0]) or sizeof(a).

The special exemption for pointers to character types gives you license
to take a pointer to an object, convert it to a pointer to a character
type, and treat the result as if it pointed to the first element of an
array whose length matches the size of the original pointed-to object.
Just that object, not an array containing it as an element.

> and according to 6.2.6.1p4, any of those objects
> can be copied into an array of unsigned char with a length equal to the
> size of the object (for example, by using memcpy()). The resulting set
> of bytes constitutes the object representation of that value. 6.5p7 says
> that an object with a given type and value can be copied, either using
> memcpy or as an array of character type, into memory with no declared
> type, and as a result produce an object whose effective type and value
> are the same as the source object.

All true, as long as the method you use for the copying doesn't involve
undefined behaviour. My point is that according to 6.3.2.3#7, the char*
pointer resulting from converting a pointer to object X can only be
incremented up to sizeof(X) times. As far as I can tell, the fact that
object X is an array element doesn't affect that limit.

> The fact that, in both clauses, the standard gives memcpy() as an
> example of how this can be done, rather than specifying that memcpy() is
> the only way to do it, implies that this is not a magic feature of
> memcpy(). A user-defined function with the same interface as memcpy(),
> which implements the same behavior defined for memcpy(), but which has a
> different name, could be used as well. That, in turn, implies that
> entirety of any object's representation may be accessed by using through
> a pointer to a character type

Of course, and 6.3.2.3#7 provides a detailed way to access them. The
key is to start with a pointer that points to the *entire* object, and
convert that pointer to a pointer to a character type. But I don't see
where the standard says that it's OK to start with a pointer to a single
array element, convert it to a pointer to char, and then use it to
access the entire array (or even just increment it to point beyond the
first byte of the second element without accessing any of the bytes).

...

James Kuyper

unread,
Jan 10, 2012, 2:48:34 PM1/10/12
to
Sorry - I missed that. While he mentioned that assumption, he made no
use of it, which made it easy to miss.

>> Pointers to character type have a special exemption from those rules,
>> though it's tricky to prove that.. p points at a, a[0], and a[0][0]. All
>> of those are objects,
>
> No, p points to an object whose type is int. It's the first element of
> the array a[0], not the entire array. The size of the object that p
> points to is sizeof(int), not sizeof(a[0]) or sizeof(a).

All you're doing is counter-asserting this. Do you have an argument to
justify failing to treat p as pointing at all three of those objects?

> The special exemption for pointers to character types gives you license
> to take a pointer to an object, convert it to a pointer to a character
> type, and treat the result as if it pointed to the first element of an
> array whose length matches the size of the original pointed-to object.
> Just that object, not an array containing it as an element.

Citation, please? As I read the standard, any char* pointer which points
at the first subobject of an aggregate object also points at the
aggregate object, and this applies recursively. The standard doesn't say
this explicitly; I consider it a natural implication of what I consider
the natural definition of "subobject". Of course, since the standard
fails to provide it's own definition for that term, there's plenty of
room for disagreement about such issues.

>> and according to 6.2.6.1p4, any of those objects
>> can be copied into an array of unsigned char with a length equal to the
>> size of the object (for example, by using memcpy()). The resulting set
>> of bytes constitutes the object representation of that value. 6.5p7 says
>> that an object with a given type and value can be copied, either using
>> memcpy or as an array of character type, into memory with no declared
>> type, and as a result produce an object whose effective type and value
>> are the same as the source object.
>
> All true, as long as the method you use for the copying doesn't involve
> undefined behaviour. My point is that according to 6.3.2.3#7, the char*
> pointer resulting from converting a pointer to object X can only be
> incremented up to sizeof(X) times. As far as I can tell, the fact that
> object X is an array element doesn't affect that limit.

It seems to me that it must affect that limit, based upon the meanings
of aggregate and subobject.

>> The fact that, in both clauses, the standard gives memcpy() as an
>> example of how this can be done, rather than specifying that memcpy() is
>> the only way to do it, implies that this is not a magic feature of
>> memcpy(). A user-defined function with the same interface as memcpy(),
>> which implements the same behavior defined for memcpy(), but which has a
>> different name, could be used as well. That, in turn, implies that
>> entirety of any object's representation may be accessed by using through
>> a pointer to a character type
>
> Of course, and 6.3.2.3#7 provides a detailed way to access them. The
> key is to start with a pointer that points to the *entire* object, and
> convert that pointer to a pointer to a character type. ...

I don't see that the standard says anything to restrict the
applicability of 6.3.2.3p7 in that fashion.

Wojtek Lerch

unread,
Jan 10, 2012, 5:29:52 PM1/10/12
to
On 10/01/2012 2:48 PM, James Kuyper wrote:
> On 01/10/2012 01:28 PM, Wojtek Lerch wrote:
>> On 10/01/2012 12:22 PM, James Kuyper wrote:
>>> On 01/10/2012 11:29 AM, Wojtek Lerch wrote:
>>>> On 10/01/2012 7:27 AM, James Kuyper wrote:
>>>>> On 01/10/2012 01:59 AM, ty ty wrote:
>>>>>> Given that sizeof(int) == 4, and the declaration:
>>>>>> int a[3][4];
>>>>>> int *p =& a[0][0];
>>>>>> int (*q)[4] =&a [0];
>>>>>> int (*r)[3][4] =&a;
>>>> ...
...
>>> Pointers to character type have a special exemption from those rules,
>>> though it's tricky to prove that.. p points at a, a[0], and a[0][0]. All
>>> of those are objects,
>>
>> No, p points to an object whose type is int. It's the first element of
>> the array a[0], not the entire array. The size of the object that p
>> points to is sizeof(int), not sizeof(a[0]) or sizeof(a).
>
> All you're doing is counter-asserting this. Do you have an argument to
> justify failing to treat p as pointing at all three of those objects?

Other than the fact that I've never seen any evidence in the C standard
that a pointer can possibly be considered to point to two or more
different objects at the same time? And the fact that the standard
often talks about "the object" that a pointer points to -- doesn't that
imply that there can only be one at any given time?

How about this one from 6.2.5#20:

"A pointer type describes an object whose value provides a reference to
an entity of the referenced type".

Doesn't that imply that what a pointer points to is an "entity of the
referenced type", as opposed to possibly an array of such entities?

>> The special exemption for pointers to character types gives you license
>> to take a pointer to an object, convert it to a pointer to a character
>> type, and treat the result as if it pointed to the first element of an
>> array whose length matches the size of the original pointed-to object.
>> Just that object, not an array containing it as an element.
>
> Citation, please?

"When a pointer to an object is converted to a pointer to a character
type, the result points to the lowest addressed byte of the object.
Successive increments of the result, up to the size of the object, yield
pointers to the remaining bytes of the object." (6.3.2.3#7)

> As I read the standard, any char* pointer which points
> at the first subobject of an aggregate object also points at the
> aggregate object, and this applies recursively.

No, any char* pointer points to a byte. When it's the first byte of an
aggregate, it also is the first byte of its first subobject. They're
the same byte, and the pointer points to it.

> The standard doesn't say
> this explicitly; I consider it a natural implication of what I consider
> the natural definition of "subobject". Of course, since the standard
> fails to provide it's own definition for that term, there's plenty of
> room for disagreement about such issues.

I don't have a problem with using the natural definition of "subobject",
and I don't think I disagree with yours.

>> All true, as long as the method you use for the copying doesn't involve
>> undefined behaviour. My point is that according to 6.3.2.3#7, the char*
>> pointer resulting from converting a pointer to object X can only be
>> incremented up to sizeof(X) times. As far as I can tell, the fact that
>> object X is an array element doesn't affect that limit.
>
> It seems to me that it must affect that limit, based upon the meanings
> of aggregate and subobject.

I don't see why. The limit is based on the type of the pointer before
the conversion to char*. That's what determines the type and size of
the object that that pointer points to. The size or declared type (if
any), or effective type (if any) of any bigger object that may contain
the pointed-to object is irrelevant. (Or have I missed a spot in the
standard that says otherwise?)

>>> The fact that, in both clauses, the standard gives memcpy() as an
>>> example of how this can be done, rather than specifying that memcpy() is
>>> the only way to do it, implies that this is not a magic feature of
>>> memcpy(). A user-defined function with the same interface as memcpy(),
>>> which implements the same behavior defined for memcpy(), but which has a
>>> different name, could be used as well. That, in turn, implies that
>>> entirety of any object's representation may be accessed by using through
>>> a pointer to a character type
>>
>> Of course, and 6.3.2.3#7 provides a detailed way to access them. The
>> key is to start with a pointer that points to the *entire* object, and
>> convert that pointer to a pointer to a character type. ...
>
> I don't see that the standard says anything to restrict the
> applicability of 6.3.2.3p7 in that fashion.

It seems that our disagreement is about whether "the size of the object"
that pointer p points to is necessarily equal to sizeof(*p) or can be
something else.

Wojtek Lerch

unread,
Jan 10, 2012, 11:10:24 PM1/10/12
to
> On 10/01/2012 2:48 PM, James Kuyper wrote:
>>> On 10/01/2012 12:22 PM, James Kuyper wrote:
>>>>> On 10/01/2012 7:27 AM, James Kuyper wrote:
>>>>>> On 01/10/2012 01:59 AM, ty ty wrote:
...
>>>>>>> int a[3][4];
>>>>>>> int *p =& a[0][0];
...
>>>> p points at a, a[0], and a[0][0].
...
>> Do you have an argument to
>> justify failing to treat p as pointing at all three of those objects?

Or how about this one: according to 6.5.3.2#4, the expression *p
designates the object that p points to. Which object would that be --
an int, the array a[0] or a, or somehow all of them? Is the answer
different for the expression a[0][0]?

Or what about 6.7.2.1#13: "A pointer to a structure object, suitably
converted, points to its initial member [...], and vice versa." If your
interpretation is correct, then a conversion is not necessary, because
the unconverted pointer already points to both the structure and its
first member, right?

Shao Miller

unread,
Jan 18, 2012, 10:28:08 AM1/18/12
to
On 1/10/2012 01:59, ty ty wrote:
> I have seen lengthy and inconclusive arguments on the use of pointers
> to an aggregate object and its sub objects, so I would like to clarify
> it here. [...]
>

Indeed. Another related thread is "Bounds Checking as Undefined Behaviour?"

Here's yet another demonstration:

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

struct one_int {
/* Room for an 'int' */
int i[1];
/* Potential padding */
};

struct two_int {
/* Room for an 'int' */
int one;
/* Potential padding */
/* Room for another 'int' */
struct one_int two;
/* Potential padding */
};

union substrate {
/* Room for 'struct two_int' */
unsigned char bytes[sizeof (struct two_int)];
/* A 'struct two_int' member */
struct two_int obj;
};

int main(void) {
union substrate test = {{0}};
int * ptr;

/* Point to the second 'int' via the array member */
ptr = test.obj.two.i;

/* Set the second 'int' */
*ptr = 42;

/*
* [Silly] UB due to bounds-checking. There is clearly enough
* room for another 'int' before the one we just assigned to.
* There's no guarantee that 'ptr' will point to the 'one'
* member, due to potential padding, but that's unimportant.
* If it _weren't_ undefined behaviour:
* - It certainly can't point to _before_ the 'one' member.
* - It certainly will point somewhere aligned for an 'int'.
* - It certainly will point to within the 'unsigned char' substrate.
* But apparently, the implementation could note that 'ptr'
* originally pointed to a particular sub-object and could disallow
* pointing to before it.
*/
ptr = ptr - 1;

printf("%p\n", (void *) ptr);

/* In fact, even this is apparently UB, for the same reason */
ptr = test.obj.two.i;
ptr = (int *) ((unsigned char *) ptr - sizeof *ptr);

printf("%p\n", (void *) ptr);

/*
* Instead, we can do something much more tedious by
* pointing at "the entire space" (via 'test.bytes') first.
*/
ptr =
(void *) (test.bytes + offsetof(struct two_int, two) - sizeof
(*ptr));

printf("%p\n", (void *) ptr);

return 0;
}

- Shao Miller

Wojtek Lerch

unread,
Jan 18, 2012, 10:47:48 AM1/18/12
to
On 18/01/2012 10:28 AM, Shao Miller wrote:
> * [Silly] UB due to bounds-checking. There is clearly enough
> * room for another 'int' before the one we just assigned to.

Sure, but if the standard says it's UB anyway, that means that compilers
can perform optimizations based on the assumption that you don't do it.
There are many people who find performance more important than the
ability to do crazy pointer math with an official blessing of the C
standard.

Shao Miller

unread,
Jan 18, 2012, 12:17:09 PM1/18/12
to
On 1/10/2012 01:59, ty ty wrote:
> I have seen lengthy and inconclusive arguments on the use of pointers
> to an aggregate object and its sub objects, so I would like to clarify
> it here. The relevant sections of C99 are these:
>
> [...references...]
>
> Given that sizeof(int) == 4, and the declaration:
> int a[3][4];
> int *p =& a[0][0];

(Which is equivalent to 'int * p = a[0] + 0;')

> int (*q)[4] =&a [0];

(Which is equivalent to 'int (* q)[4] = a + 0;')

> int (*r)[3][4] =&a;
>
> The questions are:
> a) Is the conditional expression (p == r) always true? In others
> words, is 6.5.9/6 transitive ?
>

Never true, as noted else-thread.

> b) Is (char *)p:
> i) a pointer to the first byte of the integer object a[0][0] or
> ii) a pointer to the first byte of the 1 dimensional array a [0] or
> iii) a pointer to the first byte of the 2 dimensional array a or
> iv) all of the above ?
>

(i): 'p' was initialized with the pointer value of '&a[0][0]', which is
like any of these:

int * p = &(a[0][0]);
int * p = &( (*(a + 0)) [0] );
int * p = &( (*a) [0] );
int * p = &( *((*a) + 0) );
int * p = &( *((*a)) );
int * p = &( *(*a) );
int * p = &( **a );
int * p = *a;

In the context of the last line just above, 'a' is converted to an
expression that has type 'int(*)[4]'. Application of the unary
indirection operator thus yields an expression with type 'int[4]'. That
expression is then converted to an expression with type 'int *'. The
pointed-to object is thus an 'int', which is your (i), above.

As luck would have it, '(char *) p' _also_ points to an element of an
array object with 'sizeof *p' elements of type 'char'.

> c) Following (b), do any of these lead to undefined behaviour?
> i) memset ((char*)p, 0, sizeof (*p));
> ii) memset ((char*)p, 0, sizeof (*q));
> iii) memset ((char*)p, 0, sizeof (*r));
>

Yes. (ii) and (iii) do, since the function will attempt to write beyond
the bounds of the 'sizeof *p' elements of type 'char' that '(char *) p'
points into.

> d) Since an array in an expression is converted to a pointer to its
> 1st element, and&a[0][0] points to an object of integer size, are
> these really valid:
> i) memset (a, 0, sizeof (a));

Not valid. In that context, the first appearance of 'a' is converted to
an expression with type 'int (*)[4]', which is a pointer into a space
with size 'sizeof (int[4])' bytes. The second appearance of 'a' has
type 'int[3][4]' whose size is 'sizeof (int[3][4])', which is clearly more.

> ii) memset (&a[0][0], 0, sizeof (a));

Not valid. That pointer points to a single 'int' whose size is 'sizeof
(int)' but the size that was passed is 'sizeof (int[3][4])', which is
clearly more.

> or we can only do this:
> iii) memset (&a, 0, sizeof (a));
>

You can only do that. The pointer points to an 'int[3][4]' whose size
is 'sizeof (int[3][4])', which matches the passed size.

> e) If the pointer is not to the first element, is there any
> difference? Is there undefined behaviour in this:
> i) memset (&a[1][0], 0, sizeof (a) - sizeof (a[0]));

Yes. That pointer points to a single 'int' whose size is 'sizeof (int)'
but the size that was passed is 'sizeof (int[3][4]) - sizeof (int[4])',
which is clearly more.

> ii) memset (&a[1], 0, sizeof (a) - sizeof (a[0]));
>

Yes. That pointer points to an 'int[4]' whose size is 'sizeof (int[4])'
but the size that was passed is 'sizeof (int[3][4]) - sizeof (int[4]),
which is clearly more (twice the size).

> f) When are 2 pointers, one derived from an object and another from
> the subobject considered to 'point to the same object'. Do any of
> these expressions lead to undefined behaviour?
> i) (char *)&a[0][0]< (char *)a + sizeof (a)

Yes. Before you can even do the comparison, '(char *) a + sizeof a'
yields undefined behaviour. '(char *) a' will point to both the first
byte of an 'int[4]' as well as to the first element of a 'char[sizeof
(int[4])]', but you attempt pointer arithmetic beyond that, to 'sizeof
(int[3][4])'.

> ii) (char *)&a[0][1]< (char *)a + sizeof (a)

Yes, for the same reason as (i).

> iii) (char *)&a[0][1] + 1< (char *)&a[2][0]

Yes. The first 'char *' pointer points to the first byte of an 'int' as
well as to the first element of a 'char[sizeof (int[4])]'. The second
'char *' pointer points to the first byte of a different 'int' as well
as to the first element of a different 'char[sizeof (int[4])]'. Since
these are pointing into different objects, the behaviour is undefined.

These might seem silly, but they don't hinder such things as:

int a[3][4];
int * p = *a;
p = p + 1;

Since the pointer value resulting from '*a' _does_ point to an 'int'
element of an 'int[4]'.

They do hinder:

int a[3][4];
int * p = *a;
p = p + 5;

But you could do the more tedious:

int a[3][4];
/* Point to "the whole space" first */
int * p = (void *) &a;
p = p + 5;

In that case, we know that we are pointing into the equivalent of an
'int[12]', since array elements have no padding between them.

Ugh. :(

- Shao Miller

Shao Miller

unread,
Jan 18, 2012, 12:25:44 PM1/18/12
to
On 1/18/2012 12:17, Shao Miller wrote:
> On 1/10/2012 01:59, ty ty wrote:
>
> Yes, for the same reason as (i).
>
>> iii) (char *)&a[0][1] + 1< (char *)&a[2][0]
>
> Yes. The first 'char *' pointer points to the first byte of an 'int' as
> well as to the first element of a 'char[sizeof (int[4])]'. The second
> 'char *' pointer points to the first byte of a different 'int' as well
> as to the first element of a different 'char[sizeof (int[4])]'. Since
> these are pointing into different objects, the behaviour is undefined.
>

Correction: 'char[sizeof (int)]' instead of 'char[sizeof (int[4])]' in
both places above. - Shao

Shao Miller

unread,
Jan 18, 2012, 12:43:46 PM1/18/12
to
The problem that I perceive is that you can't, in general, "get back" to
the top-level containing object, meaning 'container_of' or
'CONTAINING_RECORD' are unportable, even though they're extremely handy.

Now if the Standard detailed that conversion to a
pointer-to-character-type was guaranteed to point into the bytes of any
containing object, we'd have a situation where 'container_of' would be
portable, but optimizations for character arrays wouldn't be possible in
the same sense as you suggest. Right?

Tim Rentsch

unread,
Mar 7, 2012, 10:44:16 PM3/7/12
to
Provided an appropriate cast (eg, to void*) is done so the
comparison is not a constraint violation, yes.

> b) Is (char *)p:
> i) a pointer to the first byte of the integer object a[0][0] or
> ii) a pointer to the first byte of the 1 dimensional array a [0] or
> iii) a pointer to the first byte of the 2 dimensional array a or
> iv) all of the above ?

iv

> c) Following (b), do any of these lead to undefined behaviour?
> i) memset ((char*)p, 0, sizeof (*p));
> ii) memset ((char*)p, 0, sizeof (*q));
> iii) memset ((char*)p, 0, sizeof (*r));

Cases i and ii are defined; only iii is undefined.

> d) Since an array in an expression is converted to a pointer to its
> 1st element, and &a[0][0] points to an object of integer size, are
> these really valid:
> i) memset (a, 0, sizeof (a));
> ii) memset (&a[0][0], 0, sizeof (a));
> or we can only do this:
> iii) memset (&a, 0, sizeof (a));

only ii is undefined

> e) If the pointer is not to the first element, is there any
> difference? Is there undefined behaviour in this:
> i) memset (&a[1][0], 0, sizeof (a) - sizeof (a[0]));
> ii) memset (&a[1], 0, sizeof (a) - sizeof (a[0]));

ii is defined, i is undefined

> f) When are 2 pointers, one derived from an object and another from
> the subobject considered to 'point to the same object'. Do any of
> these expressions lead to undefined behaviour?
> i) (char *)&a[0][0] < (char *)a + sizeof (a)
> ii) (char *)&a[0][1] < (char *)a + sizeof (a)
> iii) (char *)&a[0][1] + 1 < (char *)&a[2][0]

A case could be made that they are all undefined behavior,
but practically speaking no actual implementation will
ever get any of these wrong.

Tim Rentsch

unread,
Mar 7, 2012, 11:00:57 PM3/7/12
to
Wojtek Lerch <wojt...@yahoo.ca> writes:

> On 10/01/2012 7:27 AM, James Kuyper wrote:
>> On 01/10/2012 01:59 AM, ty ty wrote:
>>> Given that sizeof(int) == 4, and the declaration:
>>> int a[3][4];
>>> int *p =& a[0][0];
>>> int (*q)[4] =&a [0];
>>> int (*r)[3][4] =&a;
> ...
>
>>> b) Is (char *)p:
>>> i) a pointer to the first byte of the integer object a[0][0] or
>>> ii) a pointer to the first byte of the 1 dimensional array a [0] or
>>> iii) a pointer to the first byte of the 2 dimensional array a or
>>> iv) all of the above ?
>>
>> iv.
>
> Right; they're all the same byte. The tricky question is how many
> times you can increment the pointer before the behaviour becomes
> undefined.
>
> If you take the standard's words literally, p points to an object of
> type int. The size of that object is four. Therefore adding five to
> (char*)p is undefined behaviour. [... snip rest]

The type of *p is irrelevant (and objects don't have a type).
And, more to the point, the semantics of pointer addition is
defined only by 6.5.6, not by 6.3.2.3p7. The point of 6.3.2.3p7
is only to say that all objects are effectively overlaid by an
array of characters; it doesn't either define pointer arithmetic
or change what region of memory can be accessed after converting
a pointer to a character pointer.

Wojtek Lerch

unread,
Mar 8, 2012, 12:25:34 AM3/8/12
to
On 07/03/2012 11:00 PM, Tim Rentsch wrote:
> Wojtek Lerch<wojt...@yahoo.ca> writes:
>> On 10/01/2012 7:27 AM, James Kuyper wrote:
>>> On 01/10/2012 01:59 AM, ty ty wrote:
>>>> Given that sizeof(int) == 4, and the declaration:
>>>> int a[3][4];
>>>> int *p =& a[0][0];
>> ...
>> If you take the standard's words literally, p points to an object of
>> type int. The size of that object is four. Therefore adding five to
>> (char*)p is undefined behaviour. [... snip rest]
>
> The type of *p is irrelevant (and objects don't have a type).

The type determines the size of the object that *p designates.

"When an object is said to have a particular type, the type is specified
by the lvalue used to designate the object." (6.3.2.1p1)

> And, more to the point, the semantics of pointer addition is
> defined only by 6.5.6, not by 6.3.2.3p7. The point of 6.3.2.3p7
> is only to say that all objects are effectively overlaid by an
> array of characters; it doesn't either define pointer arithmetic
> or change what region of memory can be accessed after converting
> a pointer to a character pointer.

It refers specifically to the size of the object that the original
pointer pointed to, and specifically limits the size of the character
array addressable by the converted character pointer to that size.

Tim Rentsch

unread,
Mar 20, 2012, 6:35:48 PM3/20/12
to
Wojtek Lerch <wojt...@yahoo.ca> writes:

> On 07/03/2012 11:00 PM, Tim Rentsch wrote:
>> Wojtek Lerch<wojt...@yahoo.ca> writes:
>>> On 10/01/2012 7:27 AM, James Kuyper wrote:
>>>> On 01/10/2012 01:59 AM, ty ty wrote:
>>>>> Given that sizeof(int) == 4, and the declaration:
>>>>> int a[3][4];
>>>>> int *p =& a[0][0];
>>> ...
>>> If you take the standard's words literally, p points to an object of
>>> type int. The size of that object is four. Therefore adding five to
>>> (char*)p is undefined behaviour. [... snip rest]
>>
>> The type of *p is irrelevant (and objects don't have a type).
>
> The type determines the size of the object that *p designates.

No, it doesn't. If that were true then 'void *' and other
incomplete pointer types such as 'struct foo *' or 'int (*)[]'
wouldn't be covered. But these cases are covered; all that is
needed (6.3.2.3 p7) is that the pointer be a pointer to an object.

> "When an object is said to have a particular type, the type is
> specified by the lvalue used to designate the object." (6.3.2.1p1)

Yes, but that's relevant only when the Standard mentions a type, not
just because you do. The requirement we are discussing here (last
two sentences of 6.3.2.3 p7) says "When a pointer to an object is
converted [...]". The type of the pointer, other than it being of
(possibly incomplete) object pointer type rather than a pointer to
function, does not enter into the requirement; all that is needed
is that the value be a pointer to an object.


>> And, more to the point, the semantics of pointer addition is
>> defined only by 6.5.6, not by 6.3.2.3p7. The point of 6.3.2.3p7
>> is only to say that all objects are effectively overlaid by an
>> array of characters; it doesn't either define pointer arithmetic
>> or change what region of memory can be accessed after converting
>> a pointer to a character pointer.
>
> It refers specifically to the size of the object that the original
> pointer pointed to, and specifically limits the size of the character
> array addressable by the converted character pointer to that size.

No, it doesn't. First, it doesn't limit the size of the array;
it says increments up to (some size) will work, but it doesn't
say increments past that size will not work. Second, there is
ambiguity about which object is being referred to. If I have a
pointer of type 'int (*)[]', cast it to 'void *', and then
convert the result to 'char *', and the array in question happens
to be the first member of a 'struct' object (of unknown contents
at the point of conversion), the Standard provides no definitive
answer to the question about how many characters can be accessed.
You might prefer your interpretation, but the only statement that
can be made absolutely is that the Standard allows more than one
possible interpretation.

More importantly, 6.3.2.3p7 doesn't override or supersede 6.5.6;
it's still 6.5.6 that says how array indexing works, and 6.3.2.3p7
just says which bytes correspond to any pointer values so obtained.
0 new messages