使用 `*p++ = *p`,C++14和C++17之间有什么区别?

36

在编写代码时,我遇到了一个问题,我设置的值被错误地设置了。最终我找到了罪魁祸首,并在测试中发现它在C++14和C++17上的行为不同。代码如下:

#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;
}

在 C++14 上,它会打印出:

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

而在 C++17 上它会输出:

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

我很好奇为什么在不同的C++版本中表现不同。看起来在C++14中,赋值语句右侧的*p会在++之后被计算。这是为什么呢?如果++具有优先级,为什么它不会在赋值运算符左侧的解引用之前发生呢?


9
在C++17中,"i--"语句返回的值是旧的值,而不是引用或指针。因此,可以将其作为右值使用。这使得 "i = i - 1" 可以写成 "i = i--",但是编译器可以任意地选择实际执行减少的顺序,这可能会导致未定义的行为。 - Zereges
1
你的代码是否能够编译,这是未指定的。因为不确定 #include <stdio.h> 后跟着 #include <cstring> 是否将函数名称 memset 放在全局作用域中。 - L. F.
2
@deW1 他们说OP包含了C++的<cstring>,但写了C的memset,这可能会在某些工具链上编译失败。OP想要的是<string.h>std::memset。(更糟糕的是,<stdio.h>也可能会传递地引入memset!) - Lightness Races in Orbit
是的,我刚才在Google上快速搜索了如何使用memset进行测试,然后在谷歌搜索结果中看到<cstring>,所以我没有多想就把它包含进来了。但实际上我应该使用std::memset的。 - ApplePearPerson
从技术上讲,你在uint8_t方面遇到了同样的问题。 - Lightness Races in Orbit
显示剩余4条评论
1个回答

58

读取变量并通过后置自增写入变量(变量的值加1),曾经存在未定义行为,因为=没有引入序列点。在C++14中可能会得到任意行为(或者没有行为,或者出现错误)。

现在,对于这种情况已经定义了顺序,因此您可以获得可靠的C++17结果。

虽然仍然是不好的代码,不应该编写不清晰的代码!


1
糟糕、不清晰的代码是不应该编写的!当然它很糟糕;顺序是错误的,与从左到右相反;我们不应该沉迷于其中。 - Kaz
2
@Kaz 实际上,从右到左的求值是很直观的。考虑 CreateObject() = MayThrow() - 如果 RHS 抛出异常,你是否期望创建一个对象?不! - Lightness Races in Orbit
2
请注意,链接中的示例与原始代码不同(尽管适用相同的标准引号),在C++17之前的i = i ++中,增量与赋值未排序,但在*p++ = *p中,增量与右操作数值计算未排序。 - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接