C: printf参数执行顺序与前置后置自增自减的影响

起源: 今天在了解副作用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=1;
    printf("%d %d\n",x,x++);
    x=1;
    printf("%d %d\n",x++,x);
    x=1;
    printf("%d %d %d\n",x,x++,x);
    x=1;
    printf("%d %d %d %d\n",x,++x,x++,x);
    return 0;
}

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

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=1;
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 1;
}

int main()
{
    int x = 1, y = 1;
    printf("x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("%d %d %d\n", x, ++x, x);
      return 0;
}

照着上面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函数

 

原文地址:https://www.cnblogs.com/Will-zyq/p/10293344.html

时间: 2024-10-06 12:06:01

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

前置后置自增自减操作

class Int { friend ostream& operator<<(ostream& os, const Int& i); friend istream& operator>>(istream& is, Int& i); friend bool operator<(const Int& a, const Int& b); private: int value; public: Int():value(0

printf的执行顺序

printf()函数的参数,在printf()函数读取时是从左往右读取的,然后将读取到的参数放到栈里面去, 最后读取到的就放在栈顶,处理参数的时候是从栈顶开始的,所以是从右边开始处理的. --printf()函数的特点. 所以printf的执行顺序是从 从右到左进行运算 注意:我们在写代码时,尽量避免无确定意义的表达式出现,因为不同的编译器,可能会采用不同的理解方式. 附上一个例子: #include<stdio.h> void main() { int i=2; printf("%

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

AOP操作是我们日常开发经常使用到的操作,例如都会用到的spring事务管理.今天我们通过一个demo实现对一个类的某一个方法进行前置和后置的增强. //被增强类 public class PetStoreService { //被增强方法 public void placeOrder(){ System.out.println("place order"); } } //增强类 public class TransactionManager { //前置方法 public void

前置后置单目运算符重载函数返回值用法

Clock& Clock::operator ++() //前置单目运算符重载函数{Second++;if(Second>=60){Second=Second-60;Minute++;if(Minute>=60){Minute=Minute-60;Hour++;Hour=Hour%24;}}return *this;}//后置单目运算符重载Clock Clock::operator ++(int) //注意形参表中的整型参数{Clock old=*this;++(*this);retu

前置和后置自增以及解引用重载函数(++、--、*)

#include<iostream> using namespace std; class INT { private: int m_i; public: INT(int i):m_i(i){} // 区分前置和后置自增重载函数的区别是是否有参数,以及参数的个数 // 如果是前置自增,比如++a,因为++符号前面没有变量,于是重载函数也就没有参数 INT& operator++() { ++(this->m_i); return *this; } const INT operat

vue路由导航守卫及前置后置钩子函数参数详解

首先构建一个测试demo如下图: 接着来探讨路由配置界面 import Vue from 'vue' import Router from 'vue-router' // import HelloWorld from '@/components/HelloWorld' Vue.use(Router) const router = new Router({ routes: [{ path: '/', name: 'HelloWorld', component: resolve => require

c++重在运算符前置自增和后置自增

class student { int age; }; int main() { class student stu; (stu++)++;//error ++(stu++);//error stu++=1;//error (++stu)++;//error ++(++stu);//error ++stu=1;//error return 0; } 前置++和后置++,有4点不同: 返回类型不同.形参不同.代码不同.效率不同 返回类型不同 前置++的返回类型是左值引用,后置++的返回类型cons

浅谈Spring AOP 面向切面编程 最通俗易懂的画图理解AOP、AOP通知执行顺序~

简介 我们都知道,Spring 框架作为后端主流框架之一,最有特点的三部分就是IOC控制反转.依赖注入.以及AOP切面.当然AOP作为一个Spring 的重要组成模块,当然IOC是不依赖于Spring框架的,这就说明你有权选择是否要用AOP来完成一些业务. AOP面向切面编程,通过另一种思考的方式,来弥补面向对象编程OOP当中的不足,OOP当中最重要的单元是类,所以万物皆对象,万物皆是 对象类.而在AOP的模块单元中,最基础的单元是切面,切面对切点进行模块化的管理. 最后再提一句:Spring当

第40课 前置操作符和后置操作符

1. ++i和i++真的有区别吗? (1)现代编译器会对代码进行优化.对于基础类型,前置++和后置++的汇编代码几乎是一样的,最终效率完全一样. (2)优化使得最终的二进制程序更加高效 (3)优化后的二进制程序丢失了C/C++的原生语义. (4)不可能从编译后的二进制程序还原C/C++程序. int i = 0; 013612FB mov dword ptr [i],0 i++; 01361302 mov eax,dword ptr [i] 01361305 add eax,1 01361308