Aliasing Struct and Array the C++ Way

Aliasing struct and array the C++ way

Use an constexpr array of pointer-to-member:

#include <math.h>

struct Point {
double x;
double y;
double z;
};

double dist(struct Point *p1, struct Point *p2) {
constexpr double Point::* coords[3] = {&Point::x, &Point::y, &Point::z};

double d2 = 0;
for (int i=0; i<3; i++) {
double d = p1->*coords[i] - p2->*coords[i];
d2 += d * d;
}
return sqrt(d2);
}

Is it legal to alias a struct and an array?

I'm gonna argue UB. First and foremost, the mandatory quote from 6.5.6 Additive operators:

When an expression that has integer type is added to or subtracted
from a pointer, the result has the type of the pointer operand. If the
pointer operand points to an element of an array object, and the array
is large enough, the result points to an element offset from the
original element such that the difference of the subscripts of the
resulting and original array elements equals the integer expression.
In other words, if the expression P points to the i-th element of an
array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N
(where N has the value n) point to, respectively, the i+n-th and
i-n-th elements of the array object, provided they exist. Moreover, if
the expression P points to the last element of an array object, the
expression (P)+1 points one past the last element of the array object,
and if the expression Q points one past the last element of an array
object
, the expression (Q)-1 points to the last element of the array
object. 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. If the result points one past the last element
of the array object, it shall not be used as the operand of a unary *
operator that is evaluated.

I emphasized what I consider the crux of the matter. You are right when you say that an array object is "a contiguously allocated nonempty set of objects with a particular member object type, called the element type". But is the converse true? Does a consecutively allocated set of objects constitute an array object?

I'm going to say no. Objects need to be explicitly created.

So for your example, there is no array object. There are generally two ways to create objects in C. Declare them with automatic, static or thread local duration. Or allocate them and give the storage an effective type. You did neither to create an array. That makes the arithmetic officially undefined.

Aliasing Arrays through structs

The answer to this question is covered in proposal: Fixing the rules for type-based aliasing which we will see, unfortunately was not resolved in 2010 when the proposal was made which is covered in Hedquist, Bativa, November 2010 minutes . Therefore C11 does not contain a resolution to N1520, so this is an open issue:

There does not seem to be any way that this matter will be
resolved at this meeting. Each thread of proposed approaches
leads to more questions. 1 1:48 am, Thurs, Nov 4, 2010.

ACTION – Clark do more work in

N1520 opens saying (emphasis mine going forward):

Richard Hansen pointed out a problem in the type-based aliasing
rules, as follows:

My question concerns the phrasing of bullet 5 of 6.5p7 (aliasing as it applies to unions/aggregates). Unless my understanding of
effective type is incorrect, it seems like the union/aggregate
condition should apply to the effective type, not the lvalue type.

Here are some more details:

Take the following code snippet as an example:

union {int a; double b;} u;
u.a = 5;

From my understanding of the definition of effective type (6.5p6), the effective type of the object at location &u is union {int a;
double b;}. The type of the lvalue expression that is accessing the
object at &u (in the second line) is int.

From my understanding of the definition of compatible type (6.2.7), int is not compatible with union {int a; double b;}, so
bullets 1 and 2 of 6.5p7 do not apply. int is not the signed or
unsigned type of the union type, so bullets 3 and 4 do not apply. int
is not a character type, so bullet 6 does not apply.

That leaves bullet 5. However, int is not an aggregate or union
type, so that bullet also does not apply. That means that the above
code violates the aliasing rule, which it obviously should not.

I believe that bullet 5 should be rephrased to indicate that if the
effective type (not the lvalue type) is an aggregate or union type
that contains a member with type compatible with the lvalue type, then
the object may be accessed.

Effectively, what he points out is that the rules are asymmetrical
with respect to struct/union membership. I have been aware of this
situation, and considered it a (non-urgent) problem, for quite some
time. A series of examples will better illustrate the problem. (These
examples were originally presented at the Santa Cruz meeting.)

In my experience with questions about whether aliasing is valid based
on type constraints, the question is invariably phrased in terms of
loop invariance. Such examples bring the problem into extremely sharp
focus.

And the relevant example that applies to this situation would be 3 which is as follows:

struct S { int a, b; };
void f3(int *pi, struct S *ps1, struct S const *ps2)
{
for (*pi = 0; *pi < 10; ++*pi) {
*ps1++ = *ps2;
}
}

The question here is whether the object *ps2 may be accessed (and
especially modified) by assigning to the lvalue *pi — and if so,
whether the standard actually says so. It could be argued that this is
not covered by the fifth bullet of 6.5p7, since *pi does not have
aggregate type at all.

**Perhaps the intention is that the question should be turned around: is
it allowed to access the value of the object pi by the lvalue ps2.
Obviously, this case would be covered by the fifth bullet.

All I can say about this interpretation is that it never occurred to
me as a possibility until the Santa Cruz meeting
, even though I've
thought about these rules in considerable depth over the course of
many years. Even if this case might be considered to be covered by the
existing wording, I'd suggest that it might be worth looking for a
less opaque formulation.

The following discussion and proposed solutions are very long and hard to summarize but seems to end with a removal of the aforementioned bullet five and resolve the issue with adjustments to other parts of 6.5. But as noted above this issues involved were not resolvable and I don't see a follow-up proposal.

So it would seem the standard wording permits the scenario the OP demonstrates although my understanding is that this was unintentional and therefore I would avoid it and it could potentially change in later standards to be non-conforming.

Aliasing array with pointer-to-struct without violating the standard

According to ISO/IEC9899/TC2 section 6.7.2.1, paragraph 13:

A pointer to a structure object, suitably converted, points to its initial member (or if that member is a bit-field, then to the unit in which it resides), and vice versa

So as long as you are casting the struct pointer to the pointer type of the first member it should not violate strict aliasing (specified in section 6.5 paragraph 7) an element may also be accessed via

an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union)

but this only works in the other direction (accessing a member via a struct pointer, not accessing a struct via a member pointer)

Can a pointer to struct alias an unsigned char array?

It does break strict aliasing. Some (saner) compilers do deduce that obviously tptr should be allowed to alias a, but this is not guaranteed by the standard. In fact, GCC will not consider tptr to be aliasing a.

Another problem is the alignment of a. The x86 is quite tolerant in that regard, but many other architectures, including the ARM, are not.

Consider (int)&a == 0xFFF02 and sizeof(int) == 4 on architecture that support fetching ints only from addresses that are themselves multiples of sizeof(int).
Such an unaligned access would result in a bus error, if you do *(int*)&a or even worse the program accessing the wrong location.

To stay clear of undefined behavior, remember that it is guaranteed that accesses through unsigned char* are possible without alignment or aliasing constraints, not the other way round. Thus use:

test buf;
can_recv((unsigned char*)&buf, sizeof buf);
if(tptr->a == 0xCDE)
{
printf("\n data received ok");

}

Another portability problem, is that you expect that bitfield to be packed a certain way. The standard doesn't guarantee that however. If you want portable behavior, restrict yourself to the bitfield interface (bitfield.b1 = 1) and don't modify the bitfied by other means.

If portability isn't a concern, make sure to set the appropriate compiler flags that guarantee the bitfield packing you're expecting.

Pointer aliasing between struct and first member of struct

This code is correct. All versions of Standard C and C++ allow this , although the wording varies.

There's no strict aliasing issue because you access an object of type int via an lvalue of type int. The strict aliasing rule may apply when the lvalue doing the access has a different type to the object stored at the memory location .

The text you quoted covers that the pointer cast actually points to the int object.

Does accessing array of POD struct as array of its single member violate strict aliasing?

reinterpret_cast<int*>(textureHandles) is definitely just as fine as &textureHandles->handle. There's a special exception in the standard, inherited from C even, that says that a pointer to a standard-layout structure, suitably converted, points to the initial member of that structure, and vice versa.

Using that to modify the handle is also fine. It doesn't violate aliasing rules, because you're using an lvalue of type int to modify a sub-object of type int.

Incrementing the resulting pointer, and using it to access other elements in an array of Texture objects, is a bit iffy, though. Jerry Coffin already pointed out that it is possible that sizeof(Texture) > sizeof(int). Even if sizeof(Texture) == sizeof(int), though, pointer arithmetic is only defined for pointers into arrays (where an arbitrary object may be considered as an array of length 1). You don't have an array of int anywhere, so the addition is simply undefined.



Related Topics



Leave a reply



Submit