0

Code:

#include <cstdio>
#include <new>

struct Foo {
  char ch;
  ~Foo() { ++ch; }
};

int main() {
  static_assert(sizeof(Foo) == 1);
  char buffer;
  auto const* pc = new (&buffer) Foo{42};
  
  // Change value using only the const pointer
  std::printf("%d\n", +buffer);
  pc->~Foo();
  std::printf("%d\n", +buffer);
}

godbolt

I am not causing any UB as far as I can tell, but GCC and Clang disagree on the result. I think the output should obviously be "42 43". That is the case for Clang, but GCC thinks the output is "42 0". How is that possible? Who zeros out the buffer? Am I missing something?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • Your code is undefined behavior, so neither compiler is wrong. – Ben Voigt Jul 02 '21 at 16:05
  • @BenVoigt could you elaborate on what's undefined? – Aykhan Hagverdili Jul 02 '21 at 16:06
  • Your expectation is wrong. Compiler discovers the result of ++ch is not used and omits the increment. You may change the behavior with `volatile char ch;`. – 273K Jul 02 '21 at 16:06
  • @S.M. then it would have printed 42 42? – Aykhan Hagverdili Jul 02 '21 at 16:07
  • Either way compiler hypothetically is allowed to `rm -rf /` your system in case of UB. Why are you expecting any explanation to UB? – Alexey S. Larionov Jul 02 '21 at 16:09
  • @AyxanHaqverdili Who said about unchanged values after the destructor? Compiler may do all what it wants with a memory after the destructor. – 273K Jul 02 '21 at 16:09
  • @AlexeyLarionov I noted that I didn't see how it's undefined. – Aykhan Hagverdili Jul 02 '21 at 16:09
  • After the `pc->~Foo();` if you were to `new (&buffer) char;` the buffer is now live, but is UB to access because it hasn't been initialized. UB to access, it could have arbitrary garbage (or possibly trip an uninitialized memory access and ABEND ... depends on the platform), or it could have the droids you are looking for. The old [hotel room](https://stackoverflow.com/a/6445794/4641116) problem. – Eljay Jul 02 '21 at 16:43
  • 1
    @Eljay My problem is that I often think of `char` as byte of memory rather than a proper object with its own lifetime and all. – Aykhan Hagverdili Jul 02 '21 at 17:00

3 Answers3

3

Your code has undefined behavior. The storage for buffer has been reused for the Foo object you created, so it's lifetime has ended and you can no longer use it. The relevent section of the standard is [basic.life]/1 with 1.5 being the relavent sub section.

The lifetime of an object o of type T ends when: [...]

  • the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Interesting. What if it was allocated though malloc? `char* buffer = (char*)malloc(1):`? – Aykhan Hagverdili Jul 02 '21 at 16:10
  • 1
    @AyxanHaqverdili Still UB because the `char` object the pointer points to will also have it's storage resused. – NathanOliver Jul 02 '21 at 16:11
  • 1
    @AyxanHaqverdili: Same problem. Note that you *can* store a char value to `buffer` legally (pedantically via `new (&buffer) char(x)`), because you do still have valid storage. It's just reading from it after killing the object that is UB. – Ben Voigt Jul 02 '21 at 16:13
2

In your final line, the lvalue buffer doesn't access any object.

The char object that was there initially had its lifetime ended by reusing its storage for a Foo. The Foo had its lifetime ended by invoking the destructor. And no one created any object in the storage after that.

lvalue-to-rvalue conversion (which is what +buffer does, but passing buffer as an argument to a variadic function would too) is not permitted where no object exists.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
2
§6.7.3.5
A program may end the lifetime of any object by reusing the
storage which the object occupies ...[cut]

You are accessing buffer after its lifetime is expired.

sp2danny
  • 7,488
  • 3
  • 31
  • 53