Why is my second snippet below showing undefined behavior?

Both clang and g++ seem compatible with the latest version of the [expr.const] / 5 paragraph in the C ++ standard. The following snippet prints 11 for both compilers. See a live example :

 #include <iostream> void f(void) { static int n = 11; static int* temp = &n; static constexpr int *&&r = std::move(temp); std::cout << *r << '\n'; } int main() { f(); } 

According to my understanding of this paragraph, both compilers should print 2016 for the code below. But they do not. Therefore, I must conclude that the code shows undefined behavior, since clang prints an arbitrary number and g++ prints 0 . I would like to know why this is UB, taking into account, for example, draft N4527 standard? Living example .

 #include <iostream> void f(void) { static int n = 11; static int m = 2016; static int* temp = &n + 1; static constexpr int *&&r = std::move(temp); std::cout << *r << '\n'; } int main() { f(); } 

Edit

I have a habit of not being satisfied with the answer, which simply says that the code is UB, or shows undefined behavior. I always like to research a little deeper, and sometimes, as now, I was lucky to understand a little more how compilers are composed. And this is what I learned in this case:

Both clang and GCC seem to exclude from the code any unused variable, for example m , for any optimization level greater than -O0 . GCC seems to arrange local variables with static storage durations, similar to how variables are pushed onto the stack, i.e. from higher to lower addresses.

Thus, in clang , if we change the optimization level to -O0 , we get the number 2016 as expected.

In GCC , if in addition to this we also change the definition

 static int* temp = &n + 1; 

to

 static int* temp = &n - 1; 

we will also get the number 2016 printed in code.

+5
source share
2 answers

I do not think that there is something subtle. &n + 1 points one to one end of the one-on-one array, which you can consider the location of n , and therefore it is not a dereferenced pointer, although it is a perfectly valid pointer. Thus, temp and r are perfectly accurate constexpr variables.

You can use r as follows:

 for (int * p = &n; p != r; ++p) { /* ... */ } 

This loop may even display in constexpr function.

The behavior, of course, is undefined when trying to dereference r , but this has nothing to do with constant expressions.

+6
source

You obviously expected that you could:

  • get a pointer to a static storage object
  • add to it
  • get a pointer to the "next" object of duration of static storage (in the order of declaration)

This is bullshit.

You will have to abandon all guaranteed standards, relying only on the wicked combination of UB and implementation documentation. It is clear that you crossed the UB threshold long before we even discussed constexpr and std::move , so I'm not sure what relevance they should have adhered to in this matter.

Pointers are not "memory addresses" that can be used to navigate through the declaration space.

+4
source

Source: https://habr.com/ru/post/1239626/


All Articles