5

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.

Belloc
  • 6,318
  • 3
  • 22
  • 52
  • 8
    Why would you expect `&n + 1` to point to `m`, or indeed anything? – Alan Stokes Jan 01 '16 at 13:14
  • @AlanStokes `n` and `m` are both in static memory and `m` follows `n`. See also [expr.const]/5 (5.2). – Belloc Jan 01 '16 at 13:19
  • 2
    In other words, I believe most of this question is a red herring, and it boils down to "why `*(&n + 1)` is undefined behaviour". – Kerrek SB Jan 01 '16 at 13:44
  • 2
    As a curatorship issue, I don't think "taking into consideration " is a useful line. You should cite the _specific_ passages you think make your program valid, not the whole document. – Lightness Races in Orbit Jan 01 '16 at 13:54

2 Answers2

6

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.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 1
    @Belloc: To be clear, there's no UB in forming `r`. There's UB in evaluating `*r`. The pointer arithmetic `&n + 1` is perfectly valid, since it produces the "one-past" pointer. – Kerrek SB Jan 01 '16 at 13:36
  • Ok, I accept your explanation for UB. But my common sense tells me that it would be reasonable to expect the second code to print `2016` from both compilers. I'm just wondering why that didn't happen? – Belloc Jan 01 '16 at 13:51
  • 4
    @Belloc: Because your "common sense" is a fantastical assumption. How did you come up with it? And are you _really_ asking why UB didn't yield the result you expected? Come on, you're better than this! :) – Lightness Races in Orbit Jan 01 '16 at 13:52
  • 2
    @Belloc: No, this would not be reasonable. There's nothing in the C++ language that would even come near suggesting this possibility. It maybe superstition or makebelief, but not reason. – Kerrek SB Jan 01 '16 at 13:53
  • @KerrekSB Both `n` and `m` have static storage duration, and "apparently" `m` follows `n` in memory. – Belloc Jan 01 '16 at 13:56
  • 2
    @Belloc: But the application of air quotes and apparition does not constitute reason. You need to follow the rules of the language, and there is nothing in those rules that suggests any such property of pointers. – Kerrek SB Jan 01 '16 at 14:03
4

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.

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055