您的问题可能不是“为什么这些构造在 C 中是未定义的行为?”。您的问题可能是“为什么此代码(使用 ++
)没有给我期望的值?”,并且有人将您的问题标记为重复,并将您发送到此处。
这个 答案试图回答这个问题:为什么你的代码没有给你期望的答案,以及你如何学会识别(和避免)那些不能按预期工作的表达式。
现在 ++
和 --
的基本定义 ++x
与后缀形式的区别 x++
。但这些运算符很难思考,所以为了确保你理解,也许你编写了一个小小的测试程序,涉及类似
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
但是,令你惊讶的是,这个程序并 没有 帮助你理解——它打印了一些奇怪的、无法解释的输出,暗示它可能 ++
做了一些完全不同的事情,根本不是你想象的那样。
或者,也许你正在看一个难以理解的表达,例如
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
也许有人把这段代码当作谜题给你。这段代码同样毫无意义,特别是当你运行它时——如果你在两个不同的编译器下编译和运行它,你很可能会得到两个不同的答案!这是怎么回事?哪个答案是正确的?(答案是两者都正确,或者都不正确。)
正如您现在所听到的,这些表达式是 未定义的 ,这意味着 C 语言无法保证它们会做什么。这是一个奇怪而令人不安的结果,因为您可能认为您编写的任何程序,只要它编译并运行,都会生成唯一、定义明确的输出。但在未定义行为的情况下,情况并非如此。
什么使得表达式未定义?涉及 ++
和的 --
是否始终未定义?当然不是:这些是有用的运算符,如果正确使用它们,它们会得到完美定义。
对于我们正在讨论的表达,当同时发生太多事情时,它们就会变得不确定,因为我们无法知道事情发生的顺序,但顺序对我们将得到的结果很重要。
让我们回顾一下我在这个回答中使用的两个例子。当我写
printf("%d %d %d\n", x, ++x, x++);
问题是,在实际调用 之前 printf
,编译器是否会先计算 的值 x
,或者 x++
,或者 ++x
?但事实证明 我们不知道 。C 中没有规则说函数的参数是从左到右、从右到左还是以其他顺序进行求值。所以我们不能说编译器会 x
先执行 ,然后 ++x
,然后 x++
,或者 x++
然后 ++x
, x
或者其他顺序。但顺序显然很重要,因为根据编译器使用的顺序,我们显然会得到不同的打印数字序列。
这疯狂的表情怎么办?
x = x++ + ++x;
这个表达式的问题在于,它包含三种不同的尝试来修改 的值 x
:(1)该 x++
部分尝试取 的 x
值,加 1,将新值存储在 中 x
,并返回旧值;(2)该 ++x
部分尝试取 的 x
值,加 1,将新值存储在 中 x
,并返回新值;(3)该 x =
部分尝试将其他两个值的总和赋回 x
。这三种尝试中的哪一种会“获胜”?这三个值中的哪一个会真正决定 的最终值 x
?同样,也许令人惊讶的是,C 中没有规则告诉我们。
您可能认为优先级、结合性或从左到右的求值会告诉您事情发生的顺序,但事实并非如此。您可能不相信我,但请相信我的话,我再说一遍:优先级和结合性并不能决定 C 中表达式求值顺序的每个方面。特别是,如果在一个表达式中有多个不同的位置我们尝试为某个东西分配一个新值,例如,优先级和结合性不会 x
告诉 我们 这些尝试中的哪一个是先发生的,哪一个是最后发生的,或者其他什么。
因此,在了解了所有这些背景知识和介绍之后,如果您想确保所有程序都定义明确,那么哪些表达式可以编写,哪些表达式不能编写?
这些表达都可以:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
这些表达式都是未定义的:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
最后一个问题是,如何判断哪些表达式是定义明确的,哪些表达式是未定义的?
正如我之前所说,未定义的表达式是那些同时发生太多事情的表达式,你不能确定事情发生的顺序,而且顺序很重要:
-
如果一个变量在两个或多个不同的地方被修改(分配),你如何知道哪个修改先发生?
-
如果某个变量在一个地方被修改,而它的值在另一个地方被使用,你怎么知道它使用的是旧值还是新值?
以 #1 为例,在表达式中
x = x++ + ++x;
有三次尝试修改 x
.
以 #2 为例,在表达式中
y = x + x++;
我们既使用 的值 x
,又对其进行修改。
所以答案就是:确保在你编写的任何表达式中,每个变量最多被修改一次,并且如果修改了变量,则不要尝试在其他地方使用该变量的值。
还有一件事。您可能想知道如何“修复”我在回答开始时提出的未定义表达式。
对于 的情况 printf("%d %d %d\n", x, ++x, x++);
,这很容易——只需将其写成三个单独的 printf
调用:
printf("%d ", x);
printf("%d ", ++x);
printf("%d\n", x++);
现在行为已经定义得非常完美,您将获得合理的结果。
对于 的情况 x = x++ + ++x
,没有办法修复它。没有办法编写它以保证其行为符合您的期望——但没关系,因为 x = x++ + ++x
无论如何您都不会像在真实程序中那样编写表达式。