通过对象的名称和引用定义明确地在表达式中多次修改对象吗?
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?
在第二个中它似乎工作正常但我对此感到困惑,因为从我所学到的;引用只是一个已经存在的对象的别名。对它的任何更改都会影响引用对象。因此,我在这里看到的是 i
和 refI
相同,因此在同一个表达式中不止一次修改同一个对象 (i
)。
但为什么所有编译器都将语句 2 视为定义明确的行为?
语句 3 (ptrI) 呢?
所有这些都会导致未定义的行为。仅仅因为编译器不发出警告并不代表它不是这样。 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(用虚线标记),因此代码定义明确。
你好我有一个简单的问题:是否在同一个表达式中多次修改一个对象;一次通过其标识符(名称),第二次通过对它的引用或指向它的指针未定义的行为?
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?
在第二个中它似乎工作正常但我对此感到困惑,因为从我所学到的;引用只是一个已经存在的对象的别名。对它的任何更改都会影响引用对象。因此,我在这里看到的是
i
和refI
相同,因此在同一个表达式中不止一次修改同一个对象 (i
)。但为什么所有编译器都将语句 2 视为定义明确的行为?
语句 3 (ptrI) 呢?
所有这些都会导致未定义的行为。仅仅因为编译器不发出警告并不代表它不是这样。 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(用虚线标记),因此代码定义明确。