printf:函数参数计算从右向左,从左向右?
造冰箱的大熊猫@cnblogs 2019/8/3
1、问题
某天写了如下代码:
unsigned char ReadByteFromFile ( FILE * fp )
{
unsigned char ch;
...
fread ( &ch, , , fp );
...
return ch;
} void main()
{
...
printf ( "first byte = 0x%02x, second byte = 0x%02x\n", ReadByteFromFile ( fp ), ReadByteFromFile ( fp ) );
...
}
printf所在行的代码本意是从文件中连续读两个字节并打印出来。假设被读取文件的内容为“0x01 02 03 04 ... ...”,那么预期的运行结果是:
first byte = 0x01, second byte = 0x02
但实际运行结果(Ubuntu,gcc编译)却颠倒了个:
first byte = 0x02, second byte = 0x01
2、解答
嗯嗯,有意思。回想了很久以前上课内容并上网搜索一番,发现C标准里没有规定编译器在计算函数参数的次序(This form of argument-passing is known as call by value. The standard does not specify any order for the
evaluation of the arguments.)。也就是说,原想着printf()在运行时按照从左向右的顺序计算参数值,在这里也就顺序读取了文件中的两个字节。但实际上,编译器输出的结果却是printf()函数按照从右向左的次序计算参数,这就导致了printf()中第一个ReadByteFromFile()函数(从左向右数)后读取文件,而第二个ReadByteFromFile()却先读取文件,最终输出结果与预想的次序颠倒。
或者用Stackoverflow上某个用户提出的问题更好地说明这一问题:为什么下面代码输出结果是“4 5 5 4 5”。
main()
{
int i = 5;
printf ( "%d %d %d %d %d %d", i++, i--, ++i, --i, i);
}
因此,在使用函数中如果涉及对同一变量/对象的多次操作,一定要考虑到编译器在处理函数参数计算时次序的不确定性。建议遇到这种情况时,还是现在函数外完成计算,再将计算结果传递给printf()。当然,如果能够约定编译器中参数计算次序(最好从左向右,与日常习惯相符),还是能省些事情,让代码看起来/写起来简洁一些。
2019.8.5补充:现在回想,好像当年上课的时候有过讲授这方面的知识还有对应的考题,但真的太久远了都忘记了。
printf:函数参数计算从右向左,从左向右?的更多相关文章
- Go 初体验 - 令人惊叹的语法 - defer.3 - defer 函数参数计算时机
defer 函数的参数计算时机 定义一个 defer 函数,接收参数 n: 调用: 输出: 有点惊讶,为什么不是 100 200 200? go 语言里,defer 函数的参数是在定义位置被计算的,也 ...
- c语言中printf()函数中的参数计算顺序
今天看到了一个关于printf()函数计算顺序的问题,首先看一个例子: #include<stdio.h> int main() { printf("%d---%d---%d&q ...
- 利用可变参数模拟Printf()函数实现一个my_print()函数和调用可变参数注意的陷阱!
可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 例如,对于函数: void test(char a ...
- 可变参数模拟printf()函数实现一个my_print()函数以及调用可变参数需注意的陷阱
入栈规则 可变参数函数的实现与函数调用的栈帧结构是密切相关的.所以在我们实现可变参数之前,先得搞清楚 栈是怎样传参的. 正常情况下,C的函数参数入栈遵照__stdcall规则, 它是从右到左的,即函数 ...
- C/C++知识要点4——printf函数以及cout的计算顺序
printf函数的计算顺序:先从右到左压栈,然后从左到右出栈. 例程: #include"stdio.h" int main() { int arr[] = { 1, 2, 3, ...
- C语言 函数参数不确定时 需要用到va_start和va_end函数
1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...);void foo(parm_list,...); 这种方式和我们以前认识的不大一样,但我 ...
- C++11的左值引用与右值引用总结
概念 在C++11中,区别表达式是左值或右值可以做这样的总结:当一个对象被用作右值的时候,用的是对象的值(内容):当对象被用作左值的时候,用的是对象的身份(在内存中的位置).左值有持久的状态,而右值要 ...
- C++11左值引用和右值引用
转载:https://www.cnblogs.com/golaxy/p/9212897.html C++11的左值引用与右值引用总结 概念 1.&与&& 对于在C++中,大家 ...
- C语言函数参数压栈顺序为何是从右到左?(从左向右的话,碰到printf的会陷入死循环)
上学期学习了汇编语言,并在操作系统实验中使用了汇编+C语言混合编程,中间也了解了一些C语言与汇编语言的对应关系. 由于汇编语言是底层的编程语言,各种函数参数都要直接控制栈进行存取,在混合编程中,要用汇 ...
随机推荐
- JS的日期的知识与格式化
需要知道的JS的日期的知识,都在这了 https://juejin.im/post/5d12b308f265da1b7c612746?utm_source=gold_browser_extension ...
- JS基础_实参可以是任何值
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Swagger学习(四、配置API文档的分组)
完整示例 代码结构 运行效果 SwaggerConfig.class @Configuration //变成配置文件 @EnableSwagger2 //开启swagger2 public class ...
- maven 私服 nexus 安装
1.去官方下载他的免费版,人民称为oss版(这一步自行百度去官网解决),官网:https://www.sonatype.com/ 2.下载好后,解压是两个文件夹: 3.配置环境变量: 4.安装生成w ...
- TypeScript入门四:TypeScript的类(class)
TypeScript类的基本使用(修饰符) TypeScript类的抽象类(abstract) TypeScript类的高级技巧 一.TypeScript类的基本使用(修饰符) TypeScript的 ...
- ASP.NET如何接收清楚缓存的通知
1 如果使用cache的add方法或者接受CacheItemPriority值得Insert方法重载接收通知,可以提供CacheItemRemovedCallBack对象,选择在清除对象时接收通知 u ...
- MYSQL AND 和 OR
AND 和 OR 如果你失忆了,希望你能想起曾经为了追求梦想的你. QQ群:651080565(php/web 学习课堂) 我们查询数据的时候,会使用条件来过滤数据,达到筛选效果,过 ...
- vs 调试时 QuickWatch 不能计算变量值
尝试下这个方法:DEBUG- -> Options and Settings--> Symbols中查看缓存路径是否设置为当前程序的bin文件然后将Debug-->Options a ...
- K2 BPM_如何将RPA的价值最大化?_全球领先的工作流引擎
自动化技术让企业能够更有效的利用资源,减少由于人为失误而造成的风险损失.随着科技的进步,实现自动化的途径变得更加多样化. 据Forrester预测,自动化技术将在2019年成为引领数字化转型的前沿技 ...
- Oracle使用基础
1.Oracle的基本概念: 数据库:存储数据的数据库,Oracle一般只有一个全局数据库 XE,ORCL. EX:Express Edition 速成版 ORCL:企业版 SID:SID是Syste ...