后增量运算符行为
这是一个测试用例:
void foo(int i, int j)
{
printf("%d %d", i, j);
}
...
test = 0;
foo(test++, test);
我期望得到“0 1”输出,但我得到“0 0” 是什么赋予了??
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
这是未指定行为的示例。 该标准没有规定参数应该按什么顺序求值。这是编译器实现的决定。 编译器可以自由地以任何顺序评估函数的参数。
在这种情况下,看起来实际上是从右到左处理参数,而不是预期的从左到右。
一般来说,在参数中产生副作用是不好的编程习惯。
而不是 foo(test++, test); 你应该写 foo(test, test+1); test++;
这在语义上等同于您想要完成的任务。
编辑:
正如安东尼正确指出的那样,在没有插入序列点的情况下读取和修改单个变量是未定义的。 所以在这种情况下,行为确实是未定义。 因此编译器可以自由地生成它想要的任何代码。
This is an example of unspecified behavior. The standard does not say what order arguments should be evaluated in. This is a compiler implementation decision. The compiler is free to evaluate the arguments to the function in any order.
In this case, it looks like actually processes the arguments right to left instead of the expected left to right.
In general, doing side-effects in arguments is bad programming practice.
Instead of foo(test++, test); you should write foo(test, test+1); test++;
It would be semantically equivalent to what you are trying to accomplish.
Edit:
As Anthony correctly points out, it is undefined to both read and modify a single variable without an intervening sequence point. So in this case, the behavior is indeed undefined. So the compiler is free to generate whatever code it wants.
这不仅仅是未指定行为,它实际上是未定义行为。
是的,参数求值的顺序未指定,但读取和修改单个变量而无需插入序列点是未定义,除非读取的目的仅仅是为了计算新值。 函数参数的求值之间没有序列点,因此
f(test,test++)
是未定义的行为:正在读取test
论证并修改为另一个。 如果将修改移至函数中,那就没问题了:这是因为进入和退出时有一个到
预增量
的序列点,因此必须在简单读取之前或之后评估调用。 现在订单只是未指定。另请注意,逗号运算符提供了一个序列点,所以
很好——增量发生在读取之前,因此
dummy
被设置为新值。This is not just unspecified behaviour, it is actually undefined behaviour .
Yes, the order of argument evaluation is unspecified, but it is undefined to both read and modify a single variable without an intervening sequence point unless the read is solely for the purpose of computing the new value. There is no sequence point between the evaluations of function arguments, so
f(test,test++)
is undefined behaviour:test
is being read for one argument and modified for the other. If you move the modification into a function then you're fine:This is because there is a sequence point on entry and exit to
preincrement
, so the call must be evaluated either before or after the simple read. Now the order is just unspecified.Note also that the comma operator provides a sequence point, so
is fine --- the increment happens before the read, so
dummy
is set to the new value.我原来所说的一切都是错误的! 计算副作用的时间点未指定。 如果 test 是局部变量,Visual C++ 将在调用 foo() 之后执行递增,但如果 test 声明为静态或全局变量,则它将在调用 foo() 之前递增并产生不同的结果,尽管最终值测试将是正确的。
增量实际上应该在调用 foo() 之后在单独的语句中完成。 即使 C/C++ 标准中指定了该行为,它也会令人困惑。 您可能认为 C++ 编译器会将其标记为潜在错误。
这里很好地描述了序列点和未指定的行为。
<----错误错误错误错误的开始---->
“test++”的“++”位在调用 foo 后执行。 因此,您将 (0,0) 传递给 foo,而不是 (1,0)
以下是 Visual Studio 2002 的汇编器输出:
增量是在调用 foo() 之后完成的。 虽然这种行为是设计使然,但它肯定会让普通读者感到困惑,应该避免。 增量实际上应该在调用 foo() 之后在单独的语句中完成
<----END OF WRONG WRONG WRONG ---->
Everything I said originally is WRONG! The point in time at which the side-affect is calculated is unspecified. Visual C++ will perform the increment after the call to foo() if test is a local variable, but if test is declared as static or global it will be incremented before the call to foo() and produce different results, although the final value of test will be correct.
The increment should really be done in a separate statement after the call to foo(). Even if the behaviour was specified in the C/C++ standard it would be confusing. You would think that C++ compilers would flag this as a potential error.
Here is a good description of sequence points and unspecified behaviour.
<----START OF WRONG WRONG WRONG---->
The "++" bit of "test++" gets executed after the call to foo. So you pass in (0,0) to foo, not (1,0)
Here is the assembler output from Visual Studio 2002:
The increment is done AFTER the call to foo(). While this behavior is by design, it is certainly confusing to the casual reader and should probably be avoided. The increment should really be done in a separate statement after the call to foo()
<----END OF WRONG WRONG WRONG ---->
这是“未指定的行为”,但在实践中,指定 C 调用堆栈的方式几乎总是保证您将其视为 0, 0,而不是 1, 0。
正如有人指出的,VC 的汇编器输出将最右边的内容推入参数首先入栈。 这就是 C 函数调用在汇编程序中的实现方式。 这是为了适应 C 的“无尽参数列表”功能。 通过按从右到左的顺序压入参数,可以保证第一个参数是堆栈顶部的项。
以 printf 的签名为例:
那些省略号表示未知数量的参数。 如果参数从左到右推送,则格式将位于堆栈的底部,而我们不知道堆栈的大小。
知道在 C(和 C++)中参数是从左到右处理的,我们可以确定解析和解释函数调用的最简单方法。 到达参数列表的末尾,然后开始推送,同时评估任何复杂的语句。
然而,即使这样也不能拯救你,因为大多数 C 编译器都有一个选项来解析“Pascal 风格”的函数。 所有这一切意味着函数参数以从左到右的方式压入堆栈。 例如,如果 printf 是使用 Pascal 选项编译的,那么输出很可能是 1, 0(但是,由于 printf 使用椭圆,我认为它不能以 Pascal 风格编译)。
It's "unspecified behavior", but in practice with the way the C call stack is specified it almost always guarantees that you will see it as 0, 0 and never 1, 0.
As someone noted, the assembler output by VC pushes the right most parameter on the stack first. This is how C function calls are implemented in assembler. This is to accommodate C's "endless parameter list" feature. By pushing parameters in a right-to-left order, the first parameter is guaranteed to be the top item on the stack.
Take printf's signature:
Those ellipses denote an unknown number of parameters. If parameters were pushed left-to-right, the format would be at the bottom of a stack of which we don't know the size.
Knowing that in C (and C++) that parameters are processed left-to-right, we can determine the simplest way of parsing and interpreting a function call. Get to the end of the parameter list, and start pushing, evaluating any complex statements as you go.
However, even this can't save you as most C compilers have an option to parse functions "Pascal style". And all this means is that the function parameters are pushed on the stack in a left-to-right fashion. If, for instance, printf was compiled with the Pascal option, then the output would most likely be 1, 0 (however, since printf uses the ellipse, I don't think it can be compiled Pascal style).
C 不保证函数调用中参数求值的顺序,因此您可能会得到结果“0 1”或“0 0”。 该顺序可以根据编译器的不同而改变,并且同一编译器可以根据优化参数选择不同的顺序。
编写 foo(test, test + 1) 然后在下一行中执行 ++test 会更安全。 无论如何,如果可能的话,编译器应该对其进行优化。
C doesn't guarantee the order of evaluation of parameters in a function call, so with this you might get the results "0 1" or "0 0". The order can change from compiler to compiler, and the same compiler could choose different orders based on optimization parameters.
It's safer to write foo(test, test + 1) and then do ++test in the next line. Anyway, the compiler should optimize it if possible.
函数参数的求值顺序未定义。 在这种情况下,它似乎是从右到左执行的。
(修改序列点之间的变量基本上允许编译器做任何它想做的事情。)
The order of evaluation for arguments to a function is undefined. In this case it appears that it did them right-to-left.
(Modifying variables between sequence points basically allows a compiler to do anything it wants.)
嗯,既然OP已经被编辑以保持一致性,它与答案不同步。 关于评估顺序的基本答案是正确的。 然而, foo(++test, test); 的具体可能值是不同的。 案件。
++test 将在通过之前递增,因此第一个参数将始终为 1。第二个参数将为 0 或 1,具体取决于计算顺序。
Um, now that the OP has been edited for consistency, it is out of sync with the answers. The fundamental answer about order of evaluation is correct. However the specific possible values are different for the foo(++test, test); case.
++test will be incremented before being passed, so the first argument will always be 1. The second argument will be 0, or 1 depending on evaluation order.
根据 C 标准,在单个序列点中对变量有多个引用是未定义的行为(这里您可以将其视为语句或函数的参数),其中多个引用之一包括前/后修改。
所以:
foo(f++,f) <--关于 f 何时递增未定义。
同样(我一直在用户代码中看到这一点):
*p = p++ + p;
通常,编译器不会更改此类事物的行为(主要修订除外)。
通过打开警告并注意它们来避免这种情况。
According to the C standard, it is undefined behaviour to have more than one references to a variable in a single sequence point (here you can think of that as being a statement, or parameters to a function) where one of more of those references includes a pre/post modification.
So:
foo(f++,f) <--undefined as to when f increments.
And likewise (I see this all the time in user code):
*p = p++ + p;
Typically a compiler will not change its behaviour for this type of thing (except for major revisions).
Avoid it by turning on warnings and paying attention to them.
重复一下其他人所说的,这不是未指定的行为,而是未定义的行为。 该程序可以合法地输出任何内容或不输出任何内容,将 n 保留为任何值,或者向您的老板发送侮辱性电子邮件。
作为实践问题,编译器编写者通常只会做他们最容易编写的事情,这通常意味着程序将获取 n 一次或两次,调用该函数,并在某个时候递增。 与任何其他可以想象的行为一样,根据标准,这很好。 没有理由期望编译器、版本或不同编译器选项之间有相同的行为。 没有理由为什么同一程序中的两个不同但外观相似的示例必须一致地编译,尽管我敢打赌就是这样。
简而言之,不要这样做。 如果您好奇的话,可以在不同的情况下进行测试,但不要假装有一个正确的甚至是可预测的结果。
To repeat what others have said, this is not unspecified behavior, but rather undefined. This program can legally output anything or nothing, leave n at any value, or send insulting email to your boss.
As a matter of practice, compiler writers will usually just do what's easiest for them to write, which generally means that the program will fetch n once or twice, call the function, and increment sometime. This, like any other conceivable behavior, is just fine according to the standard. There is no reason to expect the same behavior between compilers, or versions, or with different compiler options. There is no reason why two different but similar-looking examples in the same program have to be compiled consistently, although that's the way I'd bet.
In short, don't do this. Test it under different circumstances if you're curious, but don't pretend that there is a single correct or even predictable result.
编译器可能不会按照您期望的顺序评估参数。
The compiler might not be evaluating the arguments in the order you'd expect.