36

While writing some code, I came across a problem where values I set were being set wrong. I eventually found the culprit line and while testing around found that it behaved differently on C++14 and C++17. The code is as follows:

#include <stdio.h>
#include <cstdint>
#include <cstring>

int main()
{
    uint8_t *p = new uint8_t[3];
    memset(p, 0x00, 1);
    p++;
    memset(p, 0xF0, 1);
    p++;
    memset(p, 0xFF, 1);
    p--;
    p--;

    // This line in particular
    *p++ = *p;

    *p++ = 0x0F;

    p--;
    p--;

    printf("Position 0 has value %u\n", *p);
    p++;
    printf("Position 1 has value %u\n", *p);
    p++;
    printf("Position 2 has value %u\n", *p);

    return 0;
}

On C++14 it prints:

Position 0 has value 240
Position 1 has value 15
Position 2 has value 255

And on C++17 it prints:

Position 0 has value 0
Position 1 has value 15
Position 2 has value 255

I'm curious why it acts differently on different C++ versions. It looks as though on C++14 the *p right side of the assignment is evaluated after the ++. Did this change? And if the ++ has precedence, why does it not happen before the dereference on the left side of the assignment operator?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ApplePearPerson
  • 4,209
  • 4
  • 21
  • 36
  • 9
    [related](https://stackoverflow.com/questions/47702220/what-made-i-i-1-legal-in-c17?rq=1) – Zereges Jul 18 '19 at 09:59
  • 1
    Whether or not your code compiles is unspecified because it is unspecified whether `#include ` followed by `#include ` puts the name `memset` in the global scope. – L. F. Jul 18 '19 at 10:08
  • @L.F. I don't understand what you're trying to say – deW1 Jul 18 '19 at 10:11
  • @deW1 See https://stackoverflow.com/q/32606023 – L. F. Jul 18 '19 at 10:12
  • 2
    @deW1 They're saying that the OP included C++ `` but wrote C `memset`, and that may fail to compile with some toolchains. The OP wanted `` or `std::memset`. (And adding salt into the mix is that `` may also transitively bring in `memset`!) – Lightness Races in Orbit Jul 18 '19 at 10:19
  • Yeah I just did a quick google how to include memset for testing and the first url on google threw me at so I included that without much thought. I probably should of used `std::memset` then. – ApplePearPerson Jul 18 '19 at 10:21
  • Technically you have the same problem with `uint8_t` – Lightness Races in Orbit Jul 18 '19 at 10:56
  • If `*p++ = *p` actually did what you seem to want it to do, why would you even code it instead of `p++` (or perhaps `*p++` if the value is needed)? – Hagen von Eitzen Jul 18 '19 at 20:36
  • @HagenvonEitzen In actual code I had bitwise operators after the `*p` to modify its value and store the modified version back in the same address (then increment the address), but omitted them while trying to find the source of the problem. – ApplePearPerson Jul 19 '19 at 06:56

1 Answers1

58

Reading from and writing to the variable (via the post-increment) used to have undefined behaviour, because the = did not introduce a sequence point. You could have received either behaviour (or none, or explosions) in C++14.

Now, there is a sequencing order defined for this case and your C++17 results are reliable.

Although it's still bad, unclear code that should not be written!

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    > *bad, unclear code that should not be written!* Of course it's bad; the order is the wrong way, contrary to left-to-right; we shouldn't indulge in it. – Kaz Jul 19 '19 at 04:00
  • 2
    @Kaz Actually, right-first evaluation is intuitive. Consider `CreateObject() = MayThrow()` - do you expect an object to be created if the RHS throws? Nope! – Lightness Races in Orbit Jul 19 '19 at 10:08
  • 2
    note that the example in the link differs from the original code (although the same standard quotes apply), in `i = i++` (pre-C++17) the increment was unsequenced with the assignment, but in `*p++ = *p` the increment was unsequenced with the right operand value computation – M.M Jul 19 '19 at 10:43