您的问题可能不是,“为什么这些构造在C中未定义行为?”。您的问题可能是“为什么此代码(使用++
)没有给我期望的值?”,有人将您的问题标记为重复,然后将您发送给这里。
这个答案试图回答这个问题:为什么您的代码没有给您期望的答案,以及如何学习识别(并避免)不能按预期工作的表达式。
我想您现在已经听说了C ++
和--
运算符的基本定义,以及前缀形式++x
与postfix形式有何不同x++
。但是这些运算符很难考虑,因此为确保您理解,也许您编写了一个包含以下内容的小型测试程序:
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
但是,令您惊讶的是,该程序并没有帮助您理解-它打印了一些奇怪的,意想不到的,莫名其妙的输出,表明也许++
所做的事情完全不同,而根本没有您认为的那样。
或者,也许您正在看一个难以理解的表达式,例如
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
也许有人给您该代码令人困惑。该代码也没有意义,尤其是如果您运行它-并且如果在两个不同的编译器下编译并运行它,则可能会得到两个不同的答案!那是怎么回事?哪个答案正确?(答案是他们两个都是,或者两个都不是。)
正如您现在所听到的,所有这些表达式都是undefined,这意味着C语言不能保证它们会做什么。这是一个奇怪且令人惊讶的结果,因为您可能认为只要编写并运行该程序,任何可以编写的程序都会生成唯一的,定义明确的输出。但是对于未定义的行为,事实并非如此。
是什么使表达式不确定?表达式是否涉及++
并且--
始终未定义?当然不是:它们是有用的运算符,如果正确使用它们,它们的定义就很好。
对于表达式,我们要说的是使它们变得不确定的原因是:一次执行的事情太多,何时我们不确定事物将以什么顺序发生,但是何时顺序对结果产生影响。
让我们回到这个答案中使用的两个例子。当我写
printf("%d %d %d\n", x, ++x, x++);
问题是,在调用之前printf
,编译器是否会计算x
first或x++
or或也许的值++x
?但是事实证明我们不知道。C中没有规则说函数的参数从左到右或从右到左或以其他顺序求值。所以,我们不能说编译器是否会做x
第一个,然后++x
,然后x++
,或x++
再++x
然后x
,或其他一些订单。但是顺序显然很重要,因为根据编译器使用的顺序,我们显然会得到印刷的不同结果printf
。
那疯狂的表情呢?
x = x++ + ++x;
该表达式的问题在于,它包含三种不同的尝试来修改x的值:(1)该x++
部分尝试向x加1,将新值存储在x中x
,然后返回x 的旧值x
;(2)该++x
部分尝试将x加1,将新值存储在中x
,然后返回的新值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
,并对其进行修改。
答案就是这样:确保在您编写的任何表达式中,每个变量最多被修改一次,并且如果一个变量被修改,您也不要尝试在其他地方使用该变量的值。