#include <stdio.h>
int main(void)
{
int i = 0;
i = i++ + ++i;
printf("%d\n", i); // 3
i = 1;
i = (i++);
printf("%d\n", i); // 2 Should be 1, no ?
volatile int u = 0;
u = u++ + ++u;
printf("%d\n", u); // 1
u = 1;
u = (u++);
printf("%d\n", u); // 2 Should also be one, no ?
register int v = 0;
v = v++ + ++v;
printf("%d\n", v); // 3 (Should be the same as u ?)
int w = 0;
printf("%d %d\n", ++w, w); // shouldn't this print 1 1
int x[2] = { 5, 8 }, y = 0;
x[y] = y ++;
printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}
当前回答
这个问题通常是与代码相关的问题的重复链接,比如
printf("%d %d\n", i, i++);
or
printf("%d %d\n", ++i, i++);
或者类似的变体。
虽然正如前面所述,这也是未定义的行为,但当涉及printf()时,与如下语句进行比较时,会有细微的差异:
x = i++ + i++;
在以下声明中:
printf("%d %d\n", ++i, i++);
printf()中参数的求值顺序未指定。这意味着,表达式i++和++i可以以任何顺序求值。C11标准对此有一些相关的描述:
附件J,未指明的行为
函数指示符、参数和 参数中的子表达式在函数调用中求值 (6.5.2.2)。
3.4.4,未指定的行为
使用未指定的值,或其他行为 国际标准提供了两种或两种以上的可能性 在任何情况下都没有进一步的要求。 未指定的行为的一个例子是 函数的参数被求值。
未指定的行为本身不是问题。想想这个例子:
printf("%d %d\n", ++x, y++);
这也具有未指定的行为,因为++x和y++的求值顺序是未指定的。但这是完全合法有效的声明。在这个语句中没有未定义的行为。因为修改(++x和y++)是对不同的对象进行的。
是什么呈现下面的语句
printf("%d %d\n", ++i, i++);
作为未定义的行为是这两个表达式修改相同的对象I没有中间序列点的事实。
另一个细节是printf()调用中涉及的逗号是分隔符,而不是逗号操作符。
这是一个重要的区别,因为逗号操作符确实在它们的操作数求值之间引入了一个序列点,这使得以下行为合法:
int i = 5;
int j;
j = (++i, i++); // No undefined behaviour here because the comma operator
// introduces a sequence point between '++i' and 'i++'
printf("i=%d j=%d\n",i, j); // prints: i=7 j=6
逗号操作符从左到右计算其操作数,只产生最后一个操作数的值。所以在j = (++i, i++);中,++i将i增加到6,i++产生i(6)的旧值,该值被分配给j。然后由于后增量,i变成7。
如果函数调用中的逗号是逗号操作符,那么
printf("%d %d\n", ++i, i++);
不会有问题的。但是它调用了未定义的行为,因为这里的逗号是分隔符。
对于那些不熟悉未定义行为的人来说,阅读《每一个C程序员都应该知道关于未定义行为的事情》可以帮助他们理解C语言中未定义行为的概念和许多其他变体。
本文:未定义、未指定和实现定义的行为也是相关的。
其他回答
C标准规定,一个变量最多只能在两个序列点之间赋值一次。例如,分号是一个序列点。 所以每个形式的表述
i = i++;
i = i++ + ++i;
等等都违反了这个规则。该标准还说行为是未定义的,而不是未指定的。一些编译器确实检测这些并产生一些结果,但这不是每个标准。
但是,两个不同的变量可以在两个序列点之间递增。
while(*src++ = *dst++);
以上是在复制/分析字符串时常见的编码实践。
C有未定义行为的概念,也就是说,一些语言结构在语法上是有效的,但你不能预测代码运行时的行为。
据我所知,该标准并没有明确说明为什么存在未定义行为的概念。在我看来,这只是因为语言设计者想在语义上有一些余地,而不是要求所有实现以完全相同的方式处理整数溢出,这很可能会带来严重的性能损失,他们只是让行为未定义,所以如果你编写的代码导致整数溢出,任何事情都可能发生。
那么,考虑到这一点,为什么这些是“问题”呢?语言清楚地表明,某些事情会导致未定义的行为。没有什么问题,没有什么“应该”牵涉其中。如果当其中一个涉及的变量被声明为volatile时,未定义的行为发生了变化,这并不能证明或改变任何东西。它是没有定义的;你不能对这种行为进行推理。
你最有趣的例子就是
u = (u++);
是教科书上未定义行为的例子(见维基百科关于序列点的条目)。
在https://stackoverflow.com/questions/29505280/incrementing-array-index-in-c网站上,有人问了这样一个句子:
int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);
打印7…OP期望它打印6。
++i增量不能保证在剩余的计算之前全部完成。事实上,不同的编译器会得到不同的结果。在您提供的示例中,执行前2个++i,然后读取k[]的值,然后读取最后一个++i,然后读取k[]。
num = k[i+1]+k[i+2] + k[i+3];
i += 3
现代编译器可以很好地优化这一点。事实上,可能比您最初编写的代码更好(假设它按您希望的方式工作)。
这个问题通常是与代码相关的问题的重复链接,比如
printf("%d %d\n", i, i++);
or
printf("%d %d\n", ++i, i++);
或者类似的变体。
虽然正如前面所述,这也是未定义的行为,但当涉及printf()时,与如下语句进行比较时,会有细微的差异:
x = i++ + i++;
在以下声明中:
printf("%d %d\n", ++i, i++);
printf()中参数的求值顺序未指定。这意味着,表达式i++和++i可以以任何顺序求值。C11标准对此有一些相关的描述:
附件J,未指明的行为
函数指示符、参数和 参数中的子表达式在函数调用中求值 (6.5.2.2)。
3.4.4,未指定的行为
使用未指定的值,或其他行为 国际标准提供了两种或两种以上的可能性 在任何情况下都没有进一步的要求。 未指定的行为的一个例子是 函数的参数被求值。
未指定的行为本身不是问题。想想这个例子:
printf("%d %d\n", ++x, y++);
这也具有未指定的行为,因为++x和y++的求值顺序是未指定的。但这是完全合法有效的声明。在这个语句中没有未定义的行为。因为修改(++x和y++)是对不同的对象进行的。
是什么呈现下面的语句
printf("%d %d\n", ++i, i++);
作为未定义的行为是这两个表达式修改相同的对象I没有中间序列点的事实。
另一个细节是printf()调用中涉及的逗号是分隔符,而不是逗号操作符。
这是一个重要的区别,因为逗号操作符确实在它们的操作数求值之间引入了一个序列点,这使得以下行为合法:
int i = 5;
int j;
j = (++i, i++); // No undefined behaviour here because the comma operator
// introduces a sequence point between '++i' and 'i++'
printf("i=%d j=%d\n",i, j); // prints: i=7 j=6
逗号操作符从左到右计算其操作数,只产生最后一个操作数的值。所以在j = (++i, i++);中,++i将i增加到6,i++产生i(6)的旧值,该值被分配给j。然后由于后增量,i变成7。
如果函数调用中的逗号是逗号操作符,那么
printf("%d %d\n", ++i, i++);
不会有问题的。但是它调用了未定义的行为,因为这里的逗号是分隔符。
对于那些不熟悉未定义行为的人来说,阅读《每一个C程序员都应该知道关于未定义行为的事情》可以帮助他们理解C语言中未定义行为的概念和许多其他变体。
本文:未定义、未指定和实现定义的行为也是相关的。
虽然任何编译器和处理器实际上都不太可能这样做,但在C标准下,编译器使用以下序列实现"i++"是合法的:
In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value
虽然我不认为有任何处理器支持这样的硬件,可以有效地完成这样的事情,人们可以很容易地想象这样的行为会使多线程代码更容易(例如,它将保证如果两个线程同时尝试执行上面的序列,I将增加2),而且未来的某些处理器可能会提供类似的功能,这并不是完全不可想象的。
If the compiler were to write i++ as indicated above (legal under the standard) and were to intersperse the above instructions throughout the evaluation of the overall expression (also legal), and if it didn't happen to notice that one of the other instructions happened to access i, it would be possible (and legal) for the compiler to generate a sequence of instructions that would deadlock. To be sure, a compiler would almost certainly detect the problem in the case where the same variable i is used in both places, but if a routine accepts references to two pointers p and q, and uses (*p) and (*q) in the above expression (rather than using i twice) the compiler would not be required to recognize or avoid the deadlock that would occur if the same object's address were passed for both p and q.