Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it that my second snippet below shows undefined behavior?

Both clang and g++ seem to be compliant with the last version of the paragraph [expr.const]/5 in the C++ Standard. The following snippet prints 11 for both compilers. See 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 don't. Therefore, I must conclude that the code shows undefined behavior, as clang prints an arbitrary number and g++ prints 0. I'd like to know why is it UB, taking into consideration, for example, the draft N4527 of the Standard? Live 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 an answer that just says the code is UB, or shows undefined behavior. I always like to investigate a little deeper, and sometimes, as now, I happen to be lucky enough to understand a little bit more, how compilers are built. And that's what I found out in this case:

Both clang and GCC seem to eliminate any unused variable, like m, from the code, for any optimization level greater than -O0. GCC seems to order local variables with static storage duration, the same way variables are placed on 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 printed as expected.

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

static int* temp = &n + 1;

to

static int* temp = &n - 1;

we will also get the number 2016 printed by the code.

like image 286
Belloc Avatar asked Jan 01 '16 13:01

Belloc


2 Answers

I don't think there's anything subtle here. &n + 1 points one-past-the-end of the array-of-one as which you may consider the location n, and so it does not constitute a dereferenceable pointer, although it is a perfectly valid pointer. Thus temp and r are perfectly fine constexpr variables.

You could use r like this:

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

This loop could even appear in a constexpr function.

The behaviour is of course undefined when you attempt to dereference r, but that has nothing to do with constant expressions.

like image 77
Kerrek SB Avatar answered Oct 13 '22 00:10

Kerrek SB


You've apparently expected that you can:

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

This is nonsense.

You'd have to eschew all standard-backed guarantees, relying only on an unholy combination of UB and implementation documentation. Clearly you have crossed the UB threshold long before we ever even entertain discussions about constexpr and std::move, so I'm not sure what relevance they were intended to hold in this question.

Pointers are not "memory addresses" that you can use to navigate your declaration space.

like image 42
Lightness Races in Orbit Avatar answered Oct 12 '22 23:10

Lightness Races in Orbit