0

I want to use a stateless scope guard

struct ScopeGuard {
    ScopeGuard();
    ~ScopeGuard();
};

void f();

int test() {
    ScopeGuard scopeguard1;
    ScopeGuard scopeguard2;
    // doesn't work:
    // [[no_unique_address]] ScopeGuard scopeguard3;
    f();
}

The scope guard itself has no state, I am only interested in the side effects of the constructor/destructor.

I want test to have a stack size of 0. But scopeguard1 and scopeguard2 take 8 bytes of stack space each. This is because the this pointer needs to be unique for each instance of ScopeGuard, even if it is stateless.

Usually, I would want to use [[no_unique_address]] in this situation. But it seems [[no_unique_address]] only works for member variables but not for local variables inside functions.

Is there some other way to achieve this? Why is [[no_unique_address]] only supported for member variables? Was this an oversight in C++20? Was it intentional? Would there be any issues with allowing this attribute also for local variables?

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
Vogelsgesang
  • 684
  • 1
  • 4
  • 15
  • I don't believe a compiler needed that hint even pre-C++20. Did you *actually* check if the optimized function contains anything but function calls and the usual prolog? Member variables have object-model constraints and cannot be optimized without the hint. – StoryTeller - Unslander Monica Oct 27 '21 at 22:40
  • And if you say the c'tor and d'tor aren't inline functions... given your guard has no state then it's a simple transformation into a guard with inline functions https://godbolt.org/z/5jzxje585 – StoryTeller - Unslander Monica Oct 27 '21 at 22:48

1 Answers1

4

I want test to have a stack size of 0. But scopeguard1 and scopeguard2 take 8 bytes of stack space each. This is because the this pointer needs to be unique for each instance of ScopeGuard, even if it is stateless.

How do you know that they don't?

If the compiler can see that nothing in ScopeGuard actually cares about the value of the this pointer, and nobody actually tries to take the address of a stack variable of this type, then the compiler is 100% free to not give such a variable space on the stack.

Also, you've misunderstood part of the behavior of no_unique_address. Despite the name, it actually doesn't allow two instances of the same type to have the same address. It allows two instances of different types to overlap in member layouts. So even if you could declare those local variables with that attribute, the compiler could not given them the same address (assuming someone looked at the address at all).

no_unique_address was needed for member variables because C++ has specific rules for how a type is laid out, and each member needs its own region of storage distinct from all others. no_unique_address allowed members to break that rule, so long as the unique identity rule is satisfied.

For local variables, the compiler has the option to optimize the stack as it sees fit, so it doesn't need no_unique_address to give it permission.

And don't forget: no_unique_address doesn't provide any guarantees of anything either. It gives the compiler the option to optimize something away; it's never mandatory.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982