起源: 今天在了解副作用side-effect的过程中,看到了下面的网页,把我带到了由printf引起的一系列问题,纠结了一整天,勉强弄懂。

第一个代码没什么好解释的。而第二个printf("return of swap is %d\tx=%d,y=%d\n",swap(&x,&y),x,y)居然是"return of swap is 1  x=1,y=0",输出的x和y的值并没有改变!

原因在于C语言函数参数的处理是从右到左的压栈顺序(这个我在看第一个代码时还不知道,因为第一个代码从左往右操作的话,结果正好符合预期),但也跟编译器有关(这个下面再说)。

此时的①理解时:先处理y,y为0将0压入栈,再处理x,x为1将1压入栈内,最后处理swap,值为1将1压入栈内,所以输出的是110。

但我之后又浏览到了有关自增自减与printf的问题,

#include <stdio.h>
int main()
{
int x;
x=;
printf("%d %d\n",x,x++);
x=;
printf("%d %d\n",x++,x);
x=;
printf("%d %d %d\n",x,x++,x);
x=;
printf("%d %d %d %d\n",x,++x,x++,x);
return ;
}

按照①理解得出的答案应该是:

2 1

1 1

2 1 1

3 3 1 1

但GCC编译下来的结果是

2 1

1 2

2 1 2

3 3 1 3

为什么要特指GCC下的结果呢,因为在后来的浏览中,发现,不同的编译器会得出不同的结果(在第一个网页里有人就贴出了第一个代码在vc6.0下的结果,没重视,后来不断有这类的信息出现,自己重装了VC6.0才发现还真是不一样!)。

在VC6.0下的:

1 1
1 1
1 1 1
2 2 1 1

这更邪乎了,在vc6.0里后置符在printf里都不管用了。

经过查阅得知,在GCC下,是先处理好所有参数,然后push,在遇到后置符时会立即输出此时的值。

而VC6.0是处理好一个参数push一个且后置符在整条printf完成后才会+1。

以这条为例

x=;
printf("%d %d %d %d\n",x,++x,x++,x);

GCC下:

  处理x,此时x为1。

  处理x++:temp = x, x = x + 1此时x为2,temp为1

  处理++x:x = x + 1,此时x为3

  处理x,此时x为3

  将x,temp,x,x压入栈,然后一次弹出。结果为3 3 1 3。但如果前置符是表达式中的一部分的话,则会输出此时的值进行计算,例如:++x, ++x + 3,x(x初值是1)则输出的是3 5 1而不是3  6 1

在VC6.0下:

  处理x,此时x为1,压栈。

  处理x++,此时x为1,压栈。

  处理++x,此时x为2,压栈。

  处理x,此时x为2,压栈。

  依次弹出,输出结果为2 2 1 1,然后处理x++,此时x为3,即在后面加printf输出x的值会输出3。

但又有问题了,按照GCC的方式,则printf("return of swap is %d\tx=%d,y=%d\n",swap(&x,&y),x,y)因该是1 0 1啊,为什么x 和 y的值没有换呢,难道是因为指针?

而且我又发现在进行指针处理后,之后在prinf中只有在该变量自增自减处理之后的才遵守上面GCC的操作,而在该变量自增自减处理之前的该变量则输出当时的值。如:

#include <stdio.h>
int swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
return ;
} int main()
{
int x = , y = ;
printf("x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("%d %d %d\n", x, ++x, x);
return ;
}

照着上面GCC的处理,则printf("%d %d %d\n", x, ++x, x)应输出一样的值2 2 2,但实际在GCC编译后输出的值是2 2 1。

我是彻底搞混了,但最终的结论是:这跟编译器如何处理有关(有人说是C/C++中未定义的行为,所以不同编译器有不同的操作),且在实际中不应该写出这样的代码,重点是知道C中函数参数是从右到左的压栈顺序和自增自减是如何操作。

参考文章连接:

C语言的陷阱:关于函数参数的“副作用”问题

C语言初探 之 printf压栈顺序(printf("%d %d %d %d %d %d\n",a++, ++a, a++, ++a, a++, ++a ))

写给初学者的有关printf("%d,%d,%d,%d\n", ++a,a++,--aa--);之类的表述

i++和++i作为参数时的编译器处理方式分析~

C语言诡异的printf函数

 

C: printf参数执行顺序与前置后置自增自减的影响的更多相关文章

  1. pytest_前置后置

    今天总结下pytest,pytest简直就是python自动化中的高富帅,各种操作,哈哈 这次总结主要涉及到了以下几点: 1.unittest中的setUp.tearDown.setUpClass.t ...

  2. printf的执行顺序

    printf()函数的参数,在printf()函数读取时是从左往右读取的,然后将读取到的参数放到栈里面去, 最后读取到的就放在栈顶,处理参数的时候是从栈顶开始的,所以是从右边开始处理的. --prin ...

  3. 实现简单的AOP前置后置增强

    AOP操作是我们日常开发经常使用到的操作,例如都会用到的spring事务管理.今天我们通过一个demo实现对一个类的某一个方法进行前置和后置的增强. //被增强类 public class PetSt ...

  4. C和C++区别——前置自增与后置自增

    一.先看下面两段完全一样的代码块 /* test.cpp */ int main() { int a = 5; ++a = 7; printf("%d\n", a); return ...

  5. C++之前置自增与后置自增

    关于前置自增与后置自增的区别我是参考这里:http://bbs.bccn.net/thread-454977-1-1.html 简单复述下,比如++x; 与 x++; 在C中,++x这个表达式的值为原 ...

  6. unittest的前置后置,pytest的fixture和共享机制conftest.py

    Unittest setUp/tearDown setUp当中得到的变量,用self.xxx = value传递给测试用例 setUpClass/tearDownClass setupClass当中得 ...

  7. spring 切面 前置后置通知 环绕通知demo

    环绕通知: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http:// ...

  8. AOP 环绕通知 集成了前置 后置 返回通知等功能

    AOP 环绕通知 集成了前置 后置 返回通知等功能

  9. Spring Bean前置后置处理器的使用

    Spirng中BeanPostProcessor和InstantiationAwareBeanPostProcessorAdapter两个接口都可以实现对bean前置后置处理的效果,那这次先讲解一下B ...

随机推荐

  1. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 二十一║Vue实战:开发环境搭建【详细版】

    缘起 哈喽大家好,兜兜转转终于来到了Vue实战环节,前边的 6 篇关于Vue基础文章我刚刚简单看了看,感觉写的还是不行呀,不是很系统,所以大家可能看上去比较累,还是得抽时间去润润色,修改修改语句和样式 ...

  2. 《深入理解Java虚拟机》-----第5章 jvm调优案例分析与实战

    案例分析 高性能硬件上的程序部署策略 例 如 ,一个15万PV/天左右的在线文档类型网站最近更换了硬件系统,新的硬件为4个CPU.16GB物理内存,操作系统为64位CentOS 5.4 , Resin ...

  3. ViewPagerWithViewDemo【ViewPager和View搭配以及演示获取里面的值和CheckBox单选效果】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 简单记录下ViewPager和自定义布局view的搭配使用以及布局文件中单选效果.获取viewpager布局内部值的功能. 效果图 ...

  4. .NET Core 的缓存篇之MemoryCache

    前言 对于缓存我们都已经很熟悉了,缓存分为很多种,浏览器缓存.试图缓存.服务器缓存.数据库缓存等等一些,那今天我们先介绍一下视图缓存和MemoryCache内存缓存的概念和用法: 视图缓存 在老的版本 ...

  5. 从PRISM开始学WPF(番外)共享上下文 RegionContext-更新至Prism7.1

    RegionContext共享上下文 There are a lot of scenarios where you might want to share contextual information ...

  6. 【深度学习系列】PaddlePaddle垃圾邮件处理实战(二)

    PaddlePaddle垃圾邮件处理实战(二) 前文回顾   在上篇文章中我们讲了如何用支持向量机对垃圾邮件进行分类,auc为73.3%,本篇讲继续讲如何用PaddlePaddle实现邮件分类,将深度 ...

  7. Microsoft.Office.Interop.Excel 报错

    Microsoft.Office.Interop.Excel 报错 引用dll 在以下目录 C:\Windows\assembly\GAC_MSIL\Microsoft.Office.Interop. ...

  8. C++系列总结——封装

    前言 众所周知,封装.继承和多态是面向对象编程的三大特性.C++作为一门面向对象的编程语言,自然支持了这些特性,但C++是如何实现这些特性的呢?今天先说下我理解的封装. 封装 通常我们会把下面的行为也 ...

  9. nginx系列13:最少连接算法以及如何跨worker进程生效

    最少连接算法 使用最少连接算法可以使得nginx优先选择连接最少的上游服务器,需要用到upstream_least_conn模块. 如何跨worker进程生效 因为nginx是多进程结构的,默认多个w ...

  10. 折腾Java设计模式之观察者模式

    观察者模式 Define a one-to-many dependency between objects where a state change in one object results in ...