如何在printf占位符中使用等于表达式?
我有以下代码片段:
main( ) { int k = 35 ; printf ( "\n%d %d %d", k == 35, k = 50, k > 40 ) ; }
产生以下输出
0 50 0
我不确定我是否理解printf
的第一个值是如何变为0
。 当k
的值与35
比较时,理想情况下它应该返回(并因此打印)1,但它如何打印为零? 产生的其他两个值50
和0
都是正确的,因为在第二个值中,k的值取50
,而对于第三个值,k的值( 35
)与40
比较。 因为35 < 40
,所以它打印0。
任何帮助将不胜感激,谢谢。
**更新**
在研究了关于这个主题以及undefined behavior
,我在一本关于C的书中看到了这一点,最后给出了源代码。
调用约定调用约定表示在遇到函数调用时参数被传递给函数的顺序。 这里有两种可能性:
- 参数可能从左向右传递。
- 参数可能从右到左传递。
C语言遵循第二顺序。
考虑以下函数调用:
fun (a, b, c, d ) ;
在这个调用中,参数是从左向右还是从右向左传递并不重要。 但是,在某些函数调用中,传递参数的顺序成为一个重要的考虑因素。 例如:
int a = 1 ; printf ( "%d %d %d", a, ++a, a++ ) ;
看来这个printf( )
输出1 2 3
。 然而事实并非如此。 令人惊讶的是,它输出3 3 1
。
这是因为C的调用约定是right to left
。 也就是说,首先通过表达式a++
传递1
,然后将a
递增到2
。 然后传递++a
结果。 也就是说, a
增加到3
然后通过。 最后,传递a
最新值,即3
。 因此, right to left order
1,3,3通过。 一旦printf( )
收集它们,它就按照我们要求它们打印它们的顺序打印它们(而不是它们被传递的顺序)。 因此打印3 3 1
。
**Source: "Let Us C" 5th edition, Author: Yashwant Kanetkar, Chapter 5: Functions and Pointers**
无论这个问题是否重复,我发现这些新信息对我有帮助,所以决定分享。 注意:这也支持Mr.Zurg在下面的评论中提出的索赔。
不幸的是,对于那些读过那本书的人来说,这是完全错误的。 C99标准草案明确规定了此代码未定义的行为。 使用Wikipedia条目快速检查未定义的行为包含一个类似的示例,并将其标识为未定义。 我不会留下它,但还有其他容易获得的来源,这是正确的,而不必诉诸标准。
那标准说的是什么? 在第6.5
节中, 表达第3段说:
运算符和操作数的分组由语法表示.74)除了稍后指定的(对于函数调用(),&&,||,?:和逗号运算符),子表达式的评估顺序和顺序发生哪些副作用都是未指明的。
因此,除非指定,否则未指定子表达式的评估顺序,并且第6.5.2.2
节函数调用第10段说:
函数指示符的评估顺序,实际参数和实际参数中的子表达式是未指定的,但在实际调用之前有一个序列点。
所以在你的例子中:
printf ( "\n%d %d %d", k == 35, k = 50, k > 40 ) ; ^ ^ ^ ^ 1 2 3 4
子表达式1 to 4
可以按任何顺序进行评估,我们无法知道每个子表达式的副作用何时会发生,尽管我们知道它们都必须在实际调用函数之前生效。
因此, k = 50
可以首先评估,也可以在其间或任何地方进行评估,无论何时进行评估,改变k
值的副作用都可以在执行实际函数之后立即进行。 这意味着结果是不可预测的,可以想象在不同的执行期间改变变化。
接下来我们要解决这个问题如何成为未定义的行为。 第6.5
节第2段对此进行了说明:
在前一个和下一个序列点之间,一个对象的存储值最多只能通过表达式的计算来修改一次.72)此外,先前的值应该是只读的,以确定要存储的值.73)
因此,在您的示例中,我们不会多次修改k
,但除了确定要存储的值之外,我们还使用先前值来执行其他操作。 那么未定义行为的含义是什么? 在未定义行为的定义中,标准说:
可能的未定义行为包括完全忽略不完整结果的情况,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的特定文档执行,终止翻译或执行(发布时)一条诊断信息)。
因此编译器可以忽略它,或者也可能产生不可预测的结果,这涵盖了相当多的不良行为。 臭名昭着的说:
当编译器遇到[给定的未定义构造]时,让恶魔飞出你的鼻子是合法的
这对我来说听起来很不可取。