Is It a Strict Aliasing Violation to Alias a Struct as Its First Member

Is it a strict aliasing violation to alias a struct as its first member?

The behaviour of the cast comes down to [expr.static.cast]/13;

A prvalue of type “pointer to cv1 void” can be converted to a prvalue of type “pointer to cv2 T”, where T is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original
pointer value represents the address A of a byte in memory and A does not satisfy the alignment requirement of T , then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification) that is pointer-interconvertible with a, the result is a pointer to b. Otherwise, the pointer value is unchanged by the conversion.

The definition of pointer-interconvertible is:

Two objects a and b are pointer-interconvertible if:

  • they are the same object, or
  • one is a union object and the other is a non-static data member of that object, or
  • one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object, or
  • there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.

So in the original code, s and s.x are pointer-interconvertible and it follows that (int &)s actually designates s.x.

So, in the strict aliasing rule, the object whose stored value is being accessed is s.x and not s and so there is no problem, the code is correct.

Can a struct alias its own initial and only member?

I believe this will still violate effective typing rules. You want to access a memory location that wasn't declared explicitly (or implicitly via storage in case of dynamic allocation) as containing a struct a through an expression of that type.

None of the sections that have been quoted in other answers can be used to escape this basic restriction.

However, I believe there's a solution to your problem: Use __builtin_memcpy(), which is available even in freestanding environments (see the manual entry on -fno-builtin).


Note that the issue is a bit less clear-cut than I make it sound. C11 section 6.5 §7 tells us that it's fine to access an object through an lvalue expression that has an aggregate or union type that includes one of the aforementioned types among its members.

The C99 rationale makes it clear that this restriction is there so a pointer to an aggregate and a pointer to one of its members may alias.

I believe the ability to use this loophole in the way of the first example (but not the second one, assuming p doesn't happen to point to an actual char [4]) is an unintended consequence, which the standard only fails to disallow because of imprecise wording.

Also note that if the first example were valid, we'd basically be able to sneak in structural typing into an otherwise nominally typed language. Structures in a union with common initial subsequence aside (and even then, member names do matter), an identical memory layout is not enough to make types compatible. I believe the same reasoning applies here.

Is this strict aliasing violation? Can any type pointer alias a char pointer?

Strict aliasing means that to dereference a T* ptr, there must be a T object at that address, alive obviously. Effectively this means you cannot naively bit-cast between two incompatible types and also that a compiler can assume that no two pointers of incompatible types point to the same location.

The exception is unsigned char , char and std::byte, meaning you can reinterpret cast any object pointer to a pointer of these 3 types and dereference it.

(T*)ptr; is valid because at ptr there exists a T object. That is all that is required, it does not matter how you got that pointer*, through how many casts it went. There are some more requirements when T has constant members but that has to do more with placement new and object resurrection - see this answer if you are interested.

*It does matter even in case of no const members, probably, not sure, relevant question . @eerorika 's answer is more correct to suggest std::launder or assigning from the placement new expression.

For the record, a void* can alias any other type pointer, and any type pointer can alias a void*.

That is not true, void is not one of the three allowed types. But I assume you are just misinterpreting the word "alias" - strict aliasing only applies when a pointer is dereferenced, you are of course free to have as many pointers pointing to wherever you want as long as you do not dereference them. Since void* cannot be dereferenced, it's a moo point.

Addresing your second example

char* buffer = (char*)malloc(16); //OK

// Assigning pointers is always defined the rules only say when
// it is safe to dereference such pointer.
// You are missing a cast here, pointer cannot be casted implicitly in C++, C produces a warning only.
float* pFloat = buffer;
// -> float* pFloat =reinterpret_cast<float*>(buffer);

// NOT OK, there is no float at `buffer` - violates strict aliasing.
*pFloat = 6;
// Now there is a float
new (pFloat) float;
// Yes, now it is OK.
*pFloat = 7;

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.

Can a struct alias its own initial and only member?

I believe this will still violate effective typing rules. You want to access a memory location that wasn't declared explicitly (or implicitly via storage in case of dynamic allocation) as containing a struct a through an expression of that type.

None of the sections that have been quoted in other answers can be used to escape this basic restriction.

However, I believe there's a solution to your problem: Use __builtin_memcpy(), which is available even in freestanding environments (see the manual entry on -fno-builtin).


Note that the issue is a bit less clear-cut than I make it sound. C11 section 6.5 §7 tells us that it's fine to access an object through an lvalue expression that has an aggregate or union type that includes one of the aforementioned types among its members.

The C99 rationale makes it clear that this restriction is there so a pointer to an aggregate and a pointer to one of its members may alias.

I believe the ability to use this loophole in the way of the first example (but not the second one, assuming p doesn't happen to point to an actual char [4]) is an unintended consequence, which the standard only fails to disallow because of imprecise wording.

Also note that if the first example were valid, we'd basically be able to sneak in structural typing into an otherwise nominally typed language. Structures in a union with common initial subsequence aside (and even then, member names do matter), an identical memory layout is not enough to make types compatible. I believe the same reasoning applies here.

Shouldn't strict-aliasing kick in in this code?

Imagine the following:

struct myInt {
int i;
myInt& operator+=(int rhs) { i += rhs; return *this;}
};

void f(const int& a, myInt* dat)
{
for (int i=0; i<a; ++i)
dat[i] += a;
}

int main()
{
myInt foo{ 1 };
f(foo.i, &foo);
}

In this program, a and dat.i actually alias. They are the same variable. So the compiler actually needs to reload one after a write to the other.

What's the name of this technique and does it violate strict-aliasing rules or invoke UB?

  1. What is the name of this technique?

Obfuscation.

It has similarities with closures and with argument currying, but I wouldn't characterize it as either one.

It also has similarities with object-oriented program structure and practice, but the focus on intentionally hiding the argument types has no particular place in that regime.

And there is a hint of callback function, too.

Overall, though, it's just an over-abstracted mess.

It has been useful for passing disparate routines to another to invoke
because the invoking routine doesn't need to know the exact argument
makeup of the passed routines

I think you're fooling yourself.

Your functor_t indeed doesn't carry any information about the types that the parameters need to have, and it places only an upper bound on the number of them, but that's nothing to cheer about. The user of each instance still needs to know those things in order to use the object correctly, and the functor hides them not only from the user, but also from the compiler, such that neither one can easily check whether the user has set up the parameters correctly. The user furthermore does not benefit from any of the default argument conversions that happen in a direct function call, so they need to ensure exact type matching.

The only way I see something like this making sense is as more or less a pure callback interface, where the same user packages both the function to call and the arguments to pass to it -- or some specific ones of them, at least -- into an object, then stores or passes that off for some other function to call later. But such callback interfaces are usually structured differently, without including the function in the object alongside the arguments, and they do not go out of their way to hide data types.


  1. Does the code violate the strict-aliasing rule?

Not inherently, but strict-aliasing violations will arise if pointers to the wrong types of objects are stored in a functor's parameter members, and the functor's function is then called.


  1. Does the code invoke Undefined Bahavior?

Not inherently, but yes in the event of a strict-aliasing violation.

What is the strict aliasing rule?

A typical situation where you encounter strict aliasing problems is when overlaying a struct (like a device/network msg) onto a buffer of the word size of your system (like a pointer to uint32_ts or uint16_ts). When you overlay a struct onto such a buffer, or a buffer onto such a struct through pointer casting you can easily violate strict aliasing rules.

So in this kind of setup, if I want to send a message to something I'd have to have two incompatible pointers pointing to the same chunk of memory. I might then naively code something like this:

typedef struct Msg
{
unsigned int a;
unsigned int b;
} Msg;

void SendWord(uint32_t);

int main(void)
{
// Get a 32-bit buffer from the system
uint32_t* buff = malloc(sizeof(Msg));

// Alias that buffer through message
Msg* msg = (Msg*)(buff);

// Send a bunch of messages
for (int i = 0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendWord(buff[0]);
SendWord(buff[1]);
}
}

The strict aliasing rule makes this setup illegal: dereferencing a pointer that aliases an object that is not of a compatible type or one of the other types allowed by C 2011 6.5 paragraph 71 is undefined behavior. Unfortunately, you can still code this way, maybe get some warnings, have it compile fine, only to have weird unexpected behavior when you run the code.

(GCC appears somewhat inconsistent in its ability to give aliasing warnings, sometimes giving us a friendly warning and sometimes not.)

To see why this behavior is undefined, we have to think about what the strict aliasing rule buys the compiler. Basically, with this rule, it doesn't have to think about inserting instructions to refresh the contents of buff every run of the loop. Instead, when optimizing, with some annoyingly unenforced assumptions about aliasing, it can omit those instructions, load buff[0] and buff[1] into CPU registers once before the loop is run, and speed up the body of the loop. Before strict aliasing was introduced, the compiler had to live in a state of paranoia that the contents of buff could change by any preceding memory stores. So to get an extra performance edge, and assuming most people don't type-pun pointers, the strict aliasing rule was introduced.

Keep in mind, if you think the example is contrived, this might even happen if you're passing a buffer to another function doing the sending for you, if instead you have.

void SendMessage(uint32_t* buff, size_t size32)
{
for (int i = 0; i < size32; ++i)
{
SendWord(buff[i]);
}
}

And rewrote our earlier loop to take advantage of this convenient function

for (int i = 0; i < 10; ++i)
{
msg->a = i;
msg->b = i+1;
SendMessage(buff, 2);
}

The compiler may or may not be able to or smart enough to try to inline SendMessage and it may or may not decide to load or not load buff again. If SendMessage is part of another API that's compiled separately, it probably has instructions to load buff's contents. Then again, maybe you're in C++ and this is some templated header only implementation that the compiler thinks it can inline. Or maybe it's just something you wrote in your .c file for your own convenience. Anyway undefined behavior might still ensue. Even when we know some of what's happening under the hood, it's still a violation of the rule so no well defined behavior is guaranteed. So just by wrapping in a function that takes our word delimited buffer doesn't necessarily help.

So how do I get around this?

  • Use a union. Most compilers support this without complaining about strict aliasing. This is allowed in C99 and explicitly allowed in C11.

      union {
    Msg msg;
    unsigned int asBuffer[sizeof(Msg)/sizeof(unsigned int)];
    };
  • You can disable strict aliasing in your compiler (f[no-]strict-aliasing in gcc))

  • You can use char* for aliasing instead of your system's word. The rules allow an exception for char* (including signed char and unsigned char). It's always assumed that char* aliases other types. However this won't work the other way: there's no assumption that your struct aliases a buffer of chars.

Beginner beware

This is only one potential minefield when overlaying two types onto each other. You should also learn about endianness, word alignment, and how to deal with alignment issues through packing structs correctly.

Footnote

1 The types that C 2011 6.5 7 allows an lvalue to access are:

  • a type compatible with the effective type of the object,
  • a qualified version of a type compatible with the effective type of the object,
  • a type that is the signed or unsigned type corresponding to the effective type of the object,
  • a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
  • 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), or
  • a character type.


Related Topics



Leave a reply



Submit