造冰箱的大熊猫@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:函数参数计算从右向左,从左向右?的更多相关文章

  1. Go 初体验 - 令人惊叹的语法 - defer.3 - defer 函数参数计算时机

    defer 函数的参数计算时机 定义一个 defer 函数,接收参数 n: 调用: 输出: 有点惊讶,为什么不是 100 200 200? go 语言里,defer 函数的参数是在定义位置被计算的,也 ...

  2. c语言中printf()函数中的参数计算顺序

    今天看到了一个关于printf()函数计算顺序的问题,首先看一个例子: #include<stdio.h> int main() { printf("%d---%d---%d&q ...

  3. 利用可变参数模拟Printf()函数实现一个my_print()函数和调用可变参数注意的陷阱!

    可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈. 例如,对于函数: void test(char a ...

  4. 可变参数模拟printf()函数实现一个my_print()函数以及调用可变参数需注意的陷阱

    入栈规则 可变参数函数的实现与函数调用的栈帧结构是密切相关的.所以在我们实现可变参数之前,先得搞清楚 栈是怎样传参的. 正常情况下,C的函数参数入栈遵照__stdcall规则, 它是从右到左的,即函数 ...

  5. C/C++知识要点4——printf函数以及cout的计算顺序

    printf函数的计算顺序:先从右到左压栈,然后从左到右出栈. 例程: #include"stdio.h" int main() { int arr[] = { 1, 2, 3, ...

  6. C语言 函数参数不确定时 需要用到va_start和va_end函数

    1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表 void foo(...);void foo(parm_list,...); 这种方式和我们以前认识的不大一样,但我 ...

  7. C++11的左值引用与右值引用总结

    概念 在C++11中,区别表达式是左值或右值可以做这样的总结:当一个对象被用作右值的时候,用的是对象的值(内容):当对象被用作左值的时候,用的是对象的身份(在内存中的位置).左值有持久的状态,而右值要 ...

  8. C++11左值引用和右值引用

    转载:https://www.cnblogs.com/golaxy/p/9212897.html C++11的左值引用与右值引用总结 概念 1.&与&&  对于在C++中,大家 ...

  9. C语言函数参数压栈顺序为何是从右到左?(从左向右的话,碰到printf的会陷入死循环)

    上学期学习了汇编语言,并在操作系统实验中使用了汇编+C语言混合编程,中间也了解了一些C语言与汇编语言的对应关系. 由于汇编语言是底层的编程语言,各种函数参数都要直接控制栈进行存取,在混合编程中,要用汇 ...

随机推荐

  1. codeforces 1249C1 + 1249C2 (贪心)

    (点击此处查看原题) 题意分析 给出一个数n,求一个数m,使得m >= n ,并且m 满足: m = ∑ 3 ^ i * x (x = 0 或者 x = 1 ),求出满足条件的最小的m 解题思路 ...

  2. easyUI datagrid 刷新取消加载信息 自动刷新闪屏问题

    <style type="text/css"> /*-- 消除grid屏闪问题 --//*/ .datagrid-mask { opacity: 0; filter: ...

  3. shell习题第13题:监控nginx进程

    [题目要求] 在服务器上写一个脚本,要求如下 1. 每隔10秒去检查而一次服务器上的nginx进程数,如果>=500的时候,就需要自动重启一下nginx服务,并检测启动是否成功 2. 如没有正常 ...

  4. django API返回中文乱码

    renturn HttpResponse(json.dumps(data,ensure_ascii=False))

  5. Spring实战(二)Spring容器和bean的生命周期

    引入问题: 在XML配置文件中配置bean后,这些文件又是如何被加载的?它们被加载到哪里去了? Spring容器——框架核心 1.什么是Spring容器?它的功能是什么? 在基于Spring的应用中, ...

  6. mysql 5.7.19安装

    从mysql官网下载的mysql5.7.19免安装版, 安装时出现问题,mysql总是启动不起来,在网上查了下资料,做个记录: .将mysql解压到指定的文件夹 .以管理员身份运行cmd .定位到my ...

  7. async/await 的引用

    static async void Start() { string s = "ass"; Console.WriteLine(getMemory(s)+"Hello W ...

  8. includes()函数的用法

    在ES5,Array已经提供了indexOf用来查找某个元素的位置,如果不存在就返回-1,但是这个函数在判断数组是否包含某个元素时有两个小不足,第一个是它会返回-1和元素的位置来表示是否包含,在定位方 ...

  9. bootstrap 处理警告

    $("#id").bootstrapValidator({}).on('success.field.bv', function (e, data) { })

  10. JDK,JRE,JVM 关系和概念

    JDK : Java Development ToolKit(Java开发工具包).JDK是整个JAVA的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工 ...