这是 C/C 中未定义的行为吗? (第二部分)
关于序列点的规则对于以下代码有何说明?
int main(void) {
int i = 5;
printf("%d", ++i, i); /* Statement 1 */
}
只有一个 %d
。我很困惑,因为我在编译器 GCC、Turbo C++ 和 Visual C++ 中得到 6 作为输出。行为是否明确定义?
这与我的最后一个问题有关。
What does the rule about sequence points say about the following code?
int main(void) {
int i = 5;
printf("%d", ++i, i); /* Statement 1 */
}
There is just one %d
. I am confused because I am getting 6 as output in compilers GCC, Turbo C++ and Visual C++. Is the behavior well defined or what?
This is related to my last question.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
它未定义的原因有两个:
i
的值在没有插入序列点的情况下使用了两次(参数列表中的逗号不是逗号运算符,并且不引入序列点)。< /p>您正在调用作用域内没有原型的可变参数函数。
传递给printf()
的参数数量与格式字符串不兼容。默认输出流通常是行缓冲的。如果没有'\n'
,则无法保证输出将被有效输出。It's undefined because of 2 reasons:
The value of
i
is twice used without an intervening sequence point (the comma in argument lists is not the comma operator and does not introduce a sequence point).You're calling a variadic function without a prototype in scope.
The number of arguments passed toprintf()
are not compatible with the format string.the default output stream is usually line buffered. Without a'\n'
there is no guarantee the output will be effectively output.所有参数在调用函数时都会被求值,即使它们没有被使用,所以,由于函数参数的求值顺序是未定义的,所以你又得到了 UB。
All arguments get evaluated when calling a function, even if they are not used, so, since the order of evaluation of function arguments is undefined, you have UB again.
我认为它定义得很好。 printf 将第一个 % 占位符与第一个参数匹配,在本例中,第一个参数是预递增变量。
I think it's well defined. The printf matches the first % placeholder to the first argument, which in this instance is a preincremented variable.
所有参数都会被评估。顺序未定义。 C/C++(据我所知)的所有实现都从从右到左评估函数参数。因此,
i
通常在++i
之前计算。在 printf 中,%d 映射到第一个参数。其余的被忽略。
所以打印 6 是正确的行为。
我相信从右到左的求值顺序已经非常非常古老了(从第一个 C 编译器开始)。当然,早在 C++ 发明之前,C++ 的大多数实现都会保持相同的求值顺序,因为早期的 C++ 实现只是简单地转换为 C。
从右到左求值函数参数有一些技术原因。在堆栈架构中,参数通常被压入堆栈。在 C 中,您可以使用比实际指定的参数更多的参数来调用函数——额外的参数将被简单地忽略。如果参数从左到右求值,并从左到右压入,则堆栈指针正下方的堆栈槽将保存最后一个参数,并且函数无法获取任何特定参数的偏移量(因为推送的参数的实际数量取决于调用者)。
在从右到左的压入顺序中,堆栈指针正下方的堆栈槽将始终保存第一个参数,下一个槽保存第二个参数,依此类推。参数偏移量对于函数来说始终是确定性的(可以写成和在其他地方编译到库中,与调用它的地方分开)。
现在,从右到左的推送顺序并不强制要求从右到左的求值顺序,但在早期的编译器中,内存是稀缺的。按照从右到左的求值顺序,相同的堆栈可以就地使用(本质上是在对参数进行求值后——该参数可以是表达式或函数调用) ! -- 返回值已经位于堆栈中的正确位置)。在从左到右的计算中,参数值必须单独存储,并以相反的顺序推回堆栈。
不过,我有兴趣了解从右到左评估背后的真实历史。
All arguments are evaluated. Order not defined. All implementations of C/C++ (that I know of) evaluate function arguments from right to left. Thus
i
is usually evaluated before++i
.In printf, %d maps to the first argument. The rest are ignored.
So printing 6 is the correct behaviior.
I believe that the right-to-left evaluation order has been very very old (since the first C compilers). Certainly way before C++ was invented, and most implementations of C++ would be keeping the same evaluation order because early C++ implementations simply translates into C.
There are some technical reasons for evaluating function arguments right-to-left. In stack architectures, arguments are typically pushed onto the stack. In C, you can call a function with more arguments than actually specified -- the extra arguments are simiply ignored. If arguments are evaluated left-to-right, and pushed left-to-right, then the stack slot right under the stack pointer will hold the last argument, and there is no way for the function to get at the offset of any particular argument (because the actual number of arguments pushed depends on the caller).
In a right-to-left push order, the stack slot right under the stack pointer will always hold the first argument, and the next slot holds the second argument etc. Argument offsets will always be deterministic for the function (which may be written and compiled elsewhere into a library, separately from where it is called).
Now, right-to-left push order does not mandate right-to-left evaluation order, but in early compilers, memory is scarce. In right-to-left evaluation order, the same stack can be used in-place (essentially, after evaluating the argument -- which may be an expression or a funciton call! -- the return value is already at the right position on the stack). In left-to-right evaluation, the argument values must be stored separately and the pushed back to the stack in reverse order.
Would be interested to know the true history behind right-to-left evaluation though.
根据本文档,传递给格式字符串的任何其他参数应为被忽略。它还提到 fprintf 参数将被评估然后被忽略。我不确定 printf 是否属于这种情况。
According to this documentation, any additional arguments passed to a format string shall be ignored. It also mentions for fprintf that the argument will be evaluated then ignored. I'm not sure if this is the case with printf.