通过对象的名称和引用定义明确地在表达式中多次修改对象吗?

Is modifying an object more than once in an expression through its name and through its reference well-defined?

你好我有一个简单的问题:是否在同一个表达式中多次修改一个对象;一次通过其标识符(名称),第二次通过对它的引用或指向它的指针未定义的行为?

int i = 1;
std::cout << i << ", " << ++i << std::endl; //1- Error. Undefined Behavior

int& refI = i;
std::cout << i << ", " << ++refI << std::endl; //2-  Is this OK?

int* ptrI = &refI; // ptrI point to the referred-to object (&i)

std::cout << i << ", " << ++*ptrI << std::endl; // 3 is this also OK?

所有这些都会导致未定义的行为。仅仅因为编译器不发出警告并不代表它不是这样。 UB 可能发生任何事情:它工作,有时工作,它崩溃,它炸毁你的电脑,&c

它们在 C++17 之前都具有未定义的行为,并且在 C++17 之后都具有明确定义的行为。

请注意,在这两个示例中,您 不是 修改 i 不止一次。您仅使用增量对其进行修改。

但是,对一个标量(这里是 i 的增量)产生副作用也是未定义的行为,因为值计算是未排序的(这里是 i 的左侧使用) ).副作用是直接作用于变量还是通过引用或指针产生的都无关紧要。

在 C++17 之前,<< 运算符并不暗示其操作数的任何排序,因此您的所有示例中的行为都是未定义的。

自 C++17 起,<< 运算符保证从左到右计算其操作数。当使用运算符符号调用时,C++17 还将运算符的排序规则扩展到重载运算符。因此,在您所有的示例中,行为都是明确定义的,i 的左手使用在 i 的值递增之前首先被评估 。 =21=]

但是请注意,某些编译器并没有非常及时地实现对评估规则的这些更改,因此即使您使用 -std=c++17 标志,它仍然可能不幸地违反了旧版和当前编译器的预期行为版本。

此外,至少在 GCC 的情况下,-Wsequence-point 警告被明确记录为即使在 C++17 中定义明确的行为也会发出警告,以帮助用户避免编写代码在 C 和更早的 C++ 版本中会有未定义的行为,请参阅 GCC documentation.

编译器不需要(也不能)诊断所有未定义行为的情况。在一些简单的情况下,它可以给你一个警告(你可以使用 -Werror 或类似的方法将其变成一个错误),但在更复杂的情况下它不会。尽管如此,如果您有未定义的行为,无论是否已诊断,您的程序都将失去对其行为的任何保证。

评估规则的顺序是根据对象定义的,而不是引用或指针,或您获取对象所采用的任何方法。

也就是说,你的三个例子在评估规则的顺序上是完全等价的(如果我们只考虑定义为i的对象)。

因此让我们看看您的第一个示例:

std::cout << i << ", " << ++i << std::endl;

为简单起见,我们可以忽略 ", "std::endl,因此:

std::cout << i << ++i;

  • VC(X) = X 的值计算
  • SE(X) = X 的副作用
  • Exec(X) = X的函数体的执行
  • X <--- Y = X 在 Y 之后排序

从 c++ 11 到 c++ 17,这是未定义的行为,因为 D 的副作用(见图表)相对于 C 的值计算是无序的。但是,两者都涉及对象 i。这是未定义的行为。

从 c++ 17 开始,有一个额外的保证(在 <<>> 表达式上)C 的值计算和副作用将在值计算和副作用之前排序D(用虚线标记),因此代码定义明确。