c++中的一种无定义行为

#include 
#define sum(x, y) x + y
// 这里应当为 #define sum(x, y) ((x) + (y))
#define square(x) ((x) * (x))

int main() {
  std::cout << sum(1, 2) << ' ' << 2 * sum(3, 5) << std::endl;
  // 输出为 3 11,因为 #define 是文本替换,后面的语句被替换为了 2 * 3 + 5
  int i = 1;
  std::cout << square(++i) << ' ' << i;
  // 输出未定义,因为 ++i 被执行了两遍
  // 而同一个语句中多次修改同一个变量是未定义行为(有例外)
}讲讲这段代码里的未定义行为

在C++中,关于“同一个语句中多次修改同一个变量是未定义行为”这一规则,确实有一些例外情况。这些例外主要与**序列点(sequence point)顺序保证(sequenced before/after)**有关,尤其是在C++11及更高版本中,C++标准引入了更严格的顺序规则来替代传统的序列点概念。

1. 什么是“例外”?

在C++标准中,某些操作或表达式被明确地定义了操作顺序,即使它们看起来像是对同一个变量的多次修改。这些情况不会导致未定义行为,因为标准保证了操作的顺序。

以下是一些常见的例外情况:

1.1 逗号操作符(,

逗号操作符是一个明确的序列点。它保证了左侧表达式在右侧表达式之前求值。因此,使用逗号操作符可以避免未定义行为。

示例:

int i = 0;
i = (i++, i + 1);  // 逗号操作符保证了顺序
std::cout << i;    // 输出 1
  • i++ 先执行,将 i 修改为 1。

  • 然后执行 i + 1,结果为 2。

  • 但最终赋值给 i 的是 i + 1 的结果,因此 i 的值为 1。

这种情况下,虽然 i 被修改了两次,但逗号操作符保证了操作的顺序,因此不会导致未定义行为。

1.2 函数参数的求值顺序(C++11及更高版本)

在C++11及更高版本中,函数参数的求值顺序是未定义的,但函数调用本身是一个序列点。这意味着在函数调用中,参数的求值顺序虽然不确定,但函数调用的开始和结束之间是有顺序保证的。

示例:

int i = 0;
std::cout << std::to_string(i++) + std::to_string(i);  // 未定义行为
  • i++i 的求值顺序是未定义的,因此可能导致未定义行为。

然而,如果我们将操作放入函数调用中:

int i = 0;
f(i++, i);  // 未定义行为,因为参数求值顺序不确定
  • 这里的行为是未定义的,因为参数的求值顺序是未定义的。

但如果函数调用是明确的,比如:

int i = 0;
f(i);  // 正常调用
  • 这种情况下,函数调用本身是一个序列点,不会导致未定义行为。

1.3 条件运算符(?:

条件运算符是一个序列点。它保证了条件表达式先求值,然后根据条件的结果选择左侧或右侧的表达式求值。

示例:

int i = 0;
int result = (i == 0) ? (i = 1) : (i = 2);  // 顺序保证
std::cout << result << ' ' << i;  // 输出 1 1
  • 条件表达式 (i == 0) 先求值。

  • 根据条件的结果,选择 (i = 1)(i = 2),这里选择 (i = 1)

  • 最终结果为 1,并且 i 的值也被修改为 1

这种情况下,虽然 i 被修改了,但条件运算符保证了操作的顺序,因此不会导致未定义行为。

1.4 初始化列表(C++11及更高版本)

在C++11及更高版本中,初始化列表的求值顺序是明确的:从左到右。

示例:

int i = 0;
int arr[] = {i++, i++};  // 初始化列表保证了顺序
std::cout << arr[0] << ' ' << arr[1] << ' ' << i;  // 输出 0 1 2
  • 初始化列表保证了 i++ 的求值顺序是从左到右。

  • 因此,arr[0] 的值为 0arr[1] 的值为 1,最终 i 的值为 2

这种情况下,虽然 i 被修改了两次,但初始化列表的顺序保证使得行为是定义良好的。

2. 例外的核心原因

这些例外的核心原因是C++标准通过引入明确的顺序保证(sequenced before/after)来避免未定义行为。这些顺序保证确保了某些操作的执行顺序,即使它们看起来像是对同一个变量的多次修改。

3. 总结

在C++中,虽然“同一个语句中多次修改同一个变量”通常是未定义行为,但在某些情况下,标准通过引入明确的顺序保证来避免未定义行为。这些例外包括:

  • 使用逗号操作符(,)。

  • 条件运算符(?:)。

  • 函数调用的序列点。

  • 初始化列表的顺序保证。

理解这些例外情况可以帮助开发者更好地避免未定义行为,同时利用C++语言的灵活性。

你可能感兴趣的:(c/c++,c++,算法,开发语言)