printf函数的压栈顺序是从右到左压栈,计算表达式值的顺序也是从右到左,由于输入数据类型的多样性,压栈的时候将数据或数据的地址mov到寄存器中,然后将寄存器压栈

例子:

#include <iostream>
using namespace std;
int main(){int arr[] = { 6, 7, 8, 9, 10 };int *ptr = arr; //ptr指向6*(ptr++) += 123;//先执行6+123,然后ptr指向7printf("%d,%d\n", *ptr, *(++ptr));//先执行ptr+4使得ptr指向8,然后把ptr压栈2次printf("%d,%d,%d,%d,%d\n", *ptr--,*ptr+20, *(ptr--), *ptr, *(++ptr));int i = 5;char ch = 't';char str[10] = "test";printf("%s %c %d\n", str, ch, i);printf("%d %d %d %d\n", i, --i, i, i--);system("pause");return 0;
}

程序在VS2013中的输出如下:


分析:


上述图片中是调用printf函数时的汇编代码,主要流程是:将ptr指针增加4B,保存main的栈顶指针,把printf函数所需的2个参数压栈,调用printf函数,恢复main函数的帧顶指针。
dword表示指针是4B的,dword ptr [X]表示X所指地址对应的数据类型是双字指针。
move esi,esp表示保存main函数的栈帧的栈顶地址,以便调用printf函数返回时回到该位置处。
call dword ptr ds:[0C82184h]表示data segment寄存器中地址向后偏移0C82184h处的双字指针,此处是printf函数的代码段,call表示调用printf函数。
00C73729至00C7372E是VS编译器自带的检查栈帧是否正确恢复到原始地址的,而esp是栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶,在printf函数随着push操作而改变,因为前面往栈中push了3次,由于栈向地址减少的方向增长,那么esp就减少了12,printf结束时esp要回到mian函数的栈顶,那就是add esp, 0Ch 了。cmp指令比较esp值与esi值,结果不保存,只是修改标志寄存器值。最后__RTC_CheckEsp是调试的时候VS编译器自己加入的,发布版本会过滤掉此函数 功能就是检查堆栈平。

从上述图片中的汇编代码中可以看出,对printf函数的参数压栈过程是:
1)先从右到左计算ptr指针的变化,并把ptr–操作的结果地址保存到临时寄存器中;该步骤到00E83764 mov esi,esp结束;
2)为printf函数执行参数压栈操作,对于ptr–操作从临时寄存器中取出地址并压栈,对于其他*ptr操作和++ptr则使用ptr的最新值并压栈。

似乎也可以从中看出printf函数参数中,(++ptr)和ptr–操作的区别是:前缀++在压栈前执行,压栈时使用最新ptr值;而后缀–操作在压栈中进行并保持操作结果,压栈时使用保存的ptr值。
因此最后输出值为:8,27,9,7,7.


根据前面的解释:前缀操作在压栈前执行,压栈时使用最新指针值;后缀操作也在压栈前进行但保存结果到临时寄存器,压栈时压入临时寄存器的指针。
那么上面myprintf("%d,%d,%d,%d\n",i,–i,i,i–)代码中,从右往左执行,最后一个参数是后缀i–操作,把i的地址保存到临时寄存器,值为5;然后i的值减为4;倒数第二个参数为i,地址不用变;倒数第3个操作为–i,先把值减1得3,地址不保存;第一个操作为i,地址不保存。压栈时从右到左压栈,倒数第一个操作为i–,后缀操作,取临时寄存器的地址,值为5,压栈;倒数第2个操作,取i的最新地址,值为3,压栈;第2个参数和第1个参数都是取i的最新值,即3,压栈。输出时从栈顶到栈底依次输出,结果为:5,3,3,3。

说明:

真正开始压栈操作是从mov esi,esp开始。
eax是加法寄存器
mov eax,dword ptr[ptr] 是源操作数的间接寻址。

360笔试题如下

int a[] = { 3, 4 }, b[] = { 5, 6 };
struct st{int num;int *p;
}s[2] = {100,a,100,b},*q = s;
int main(){int n;printf("%d%d%d",*q->p,++q->num,(q++)->num);return 0;
}

输出:5101100

解释:压栈前,计算q的地址变化,q初始时指向s[0],在printf函数的参数中,首先是q后缀++操作,需要先保存q的地址到临时寄存器中,然后执q++,这时q指向S[1],并把地址保存;然后是++q->num操作,++操作优先级低于->操作,因此是把s[1].num进行加运算,值由100变为101,修改的不是q的值;最后是*q->p操作,q地址没变;压栈时,从右到左,首先取临时寄存器中q的地址,即S[0].num,值100压栈;最新q指向s[1],然后取s[1].num,值为101压栈;最后取s[1].p的值,s[1].p指向数组b,值为5压栈。最后从栈顶到栈底输出,即:5,101,100.

printf压栈出栈相关推荐

  1. c语言局部变量压栈顺序,C 程序局部变量压栈出栈的理解

    写这篇总结的缘由仅仅出于巧合,五一前帮一位同学看51的程序,在查看汇编代码的时候(事实上我当时的汇编知识基本都还给了老师),无意中问起我"某个局部变量的声明怎么没有对应的汇编语句" ...

  2. Java 数据结构(链表LinkedList增删改查、数组Vector、获取Vector最大值、交换Vector两成员位置、栈的实现、压栈出栈实现反转、队列Queue)

    在链表(LinkedList)的开头和结尾添加元素 import java.util.LinkedList;public class Main {public static void main(Str ...

  3. java压栈重复字符串_Java 实例 – 压栈出栈的方法实现字符串反转

    Java 实例 - 压栈出栈的方法实现字符串反转 以下实例演示了使用用户自定义的方法 StringReverserThroughStack() 来实现字符串反转: /* author by w3csc ...

  4. 【OpenGL】二十一、OpenGL 矩阵压栈与出栈 ( 不同类型矩阵变换先后顺序 | 渲染前不设置单位阵 | 压栈出栈原理分析 | 代码示例 )

    文章目录 一.不同类型矩阵变换先后顺序 二.渲染前不设置单位阵 三.矩阵的压栈和出栈原理分析 四.矩阵的压栈和出栈代码示例 五.相关资源 一.不同类型矩阵变换先后顺序 对 OpenGL 中的 模型视图 ...

  5. 【数据结构】栈详解——压栈/入栈 | 弹栈/出栈 | 获取栈顶元素

    栈 顺序栈 栈的定义 栈(stack)又名堆栈,它是一种运算受限的线性表.限定仅在表尾进行插入和删除操作的线性表.这一端被称为栈顶,相对地,把另一端称为栈底.向一个栈插入新元素又称作进栈.入栈或压栈, ...

  6. JVM常用指令:常量,变量的压栈出栈指令

    目录 常量压栈指令 局部变量表压栈指令 操作数栈出栈指令 通用指令 上一篇日志里用到的指令如bipush,iload等都是JVM常用的指令,它们有各自的分类,如bipush是常量压入操作数栈,iloa ...

  7. Go语言_数据结构_栈(包括入栈和出栈,表达式的入栈出栈详细过程代码实现)

    入栈和出栈代码实现如下: package main import ("fmt""errors" )//使用数组来模拟一个栈的使用 type Stack stru ...

  8. 数据结构 - 栈(链表实现栈的入栈出栈)

    学完链表和栈,数组模拟栈学完后,自己根据链表写了个链栈,虽然只是简单的实现了入栈出栈遍历.收获还是很大的. 在push这里思考了一会,思路是:传入一个数字,push里创建一个节点node,把数据加入n ...

  9. 栈的初始化,入栈,出栈,遍历操作(代码实现) [数据结构][Java]

    栈的初始化,入栈,出栈,遍历操作(代码实现) 具体代码如下: package com.ffyc.stack;/*** 创建一个栈结构(使用数组实现)*/ public class ArrayStack ...

  10. 【Android 逆向】x86 汇编 ( push / pop 入栈 / 出栈 指令 | ret / retn 函数调用返回指令 | set 设置目标值指令 )

    文章目录 一.push / pop 入栈 / 出栈 指令 二.ret / retn 函数调用返回指令 三.set 设置目标值指令 总结 一.push / pop 入栈 / 出栈 指令 push / p ...

最新文章

  1. linux笔记本没有insert,无法在Linux中将UTF8插入数据库MySQL(Can not insert UTF8 to Database MySQL in Linux)...
  2. kettle 日志存到mysql_kettle作业(job)调用转换,设置变量,写日志到数据库中【转】...
  3. java修改JFrame默认字体
  4. GCD 深入理解:第一部分
  5. cloud 异步远程调用_异步远程工作的意外好处-以及如何拥抱它们
  6. filepermission java,Java FilePermission getActions()方法与示例
  7. aop java 接口_Spring AOP实现接口验签
  8. 盒子模型代码_果冻公开课第五课:五分钟理清盒模型的前世今生
  9. Linux之find学习--详解
  10. Python 学习第一周
  11. 计算机网络(第八版) 谢希仁——知识点
  12. 游戏计算机重要参数,这真的很重要吗 游戏鼠标三大参数之谜
  13. 02 python入门
  14. Mysql8.0下载(网盘云盘)
  15. 16种设计思想 - Design for failure
  16. 新浪微博2012校招笔试题
  17. 传说中的移动员工工资表,也不知是真是假
  18. Java变态题目(持续更新)
  19. 安徽省计算机二级各部分分值分布,计算机二级各部分分值
  20. ORACLE:子查询

热门文章

  1. NLP学习笔记6--Lecture/语言模型/预处理/独热编码/word2vec/文本特征工程
  2. 解读西门子的工业软件帝国,巨头的数字化工业战略
  3. 微信平台开发——日历服务
  4. windows下判断网卡是否是虚拟网卡的方式
  5. 尝试寻找一些合作伙伴,产品相关培训咨询服务介绍(2B/支持在线)
  6. 我的世界服务器怎么添加信息框,我的世界服务器怎么添加指定建筑
  7. 虚拟机创建和配置的详细步骤
  8. unity加入屏幕左右边界碰撞体
  9. ubuntu 20使用kubeadm安装k8s 1.26
  10. cannot mysql server on_轻松解决cant connect to MySQL server on 'xxx'(10038)错误