C++20 [[no_unique_address]] Attribute

Written by on June 26, 2022 in C++, Programming with 0 Comments

The [[no_unique_address]] attribute was introduced in C++20 to give a compiler the freedom to optimise memory allocation of a struct/class when it contains a subobject that does not have any members. The other requirement is that the subobject should not be a static member of the enclosing struct/class.

Let us start with the basics first. Consider the following example:

Empty vs. Non-empty Struct

Empty vs. Non-empty Struct

In the example above, EmptyStruct does not have any members at all. So when an object of this type is defined, should the compiler allocate any memory for the object? The language says that it should. In other words, the sizeof(EmptyStruct) must be at least 1 byte. 

Let us look at Struct1. This is a normal struct with an integer member. In this case, we expect sizeof(Struct1) to be equal to sizeof(int). In general, due to padding issues, the size of the composite object could be greater than the sum of the individual objects.

What about Struct2? Here, we have a member of type EmptyStruct in addition to an integer. So what should be the sizeof(Struct2)? Should it allocate any memory for the contained EmptyStruct member es? By default, the compiler will allocate memory for that member as well. 

When the above program is executed, here is the output:

Program Output

Program Output

It is worth noting that the compiler has allocated 8 bytes for object of type Struct2, while 4 bytes would have been sufficient (as in Struct1) theoretically.

Here is where [[no_unique_address]] attribute comes in to picture. We can annotate a member subobject of a class with this attribute, if we feel that there is no need for extra memory for that object (when it is empty) inside the enclosing object.

A minor digression at this point. I am using Visual Studio 2022 Ver 17.2.3 (64 bit) running in Windows 10. In this version, Microsoft recommends using the qualified attribute [[msvc::no_unique_address]] rather than the standard mandated [[no_unique_address]] attribute, for certain binary compatibility reasons (this will change in the future). So that is the attribute I have used in my code. For convenience, I am using this #define:

#define NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]

OK, now take a look at the following code:

Using no_unique_address Attribute

Using no_unique_address Attribute

In this example, Struct3 has the EmptyStruct es subobject marked as NO_UNIQUE_ADDRESS. Here is the output from the program:

Effect of no_unique_address

Effect of no_unique_address

As expected, the compiler has only allocated 4 bytes (size of int) for Struct3 object. What is interesting is that the addresses of s3.i and s3.es are the same! This confirms that no extra memory has been reserved for s3.es.

The next example introduces an additional twist. 

Multiple Empty Subobjects

Multiple Empty Subobjects

Struct4 has two EmptyStruct members es and es2, and both are marked NO_UNIQUE_ADDRESS. What will be the size of a Struct4 object and what will be the addresses of the three subobjects? Let us see the output:

Program Output

Program Output

The size of the whole struct is just 4 bytes. It implies that the compiler has allocated space only for the int i field. If you look at the addresses, it is clear that the memory space allocated for the int i object is shared with the two EmptyStruct objects, at the same time guaranteeing that the address of es is different from that of es2! Clearly, this memory optimisation is only because of NO_UNIQUE_ADDRESS attribute.

One last example before we end this article. What happens if we annotate a non-empty subobject with NO_UNIQUE_ADDRESS attribute? The attribute will be obviosly ignored by the compiler. 

Here is the example:

Non Empty Struct

Non Empty Struct

Here is the corresponding output:

no_unique_address Ignored

no_unique_address Ignored

 The output shows that extra space has been allocated for the NonEmptyStruct subobject and the addresses of the subobjects do not overlap.

To recap what we have discussed so far, C++ 20 allows us to use the [[no_unique_address]] attribute on non-static and empty subobjects in a class/struct to give a hint to the compiler to avoid allocating memory for the subobject. This is useful when targeting a resource-constrained environment such as an embedded controller.

You can download the example source code here.

Have a great weekend!

Tags: ,

Subscribe

If you enjoyed this article, subscribe now to receive more just like it.

Subscribe via RSS Feed

Leave a Reply

Your email address will not be published.

Top