最开始写这篇文章的时候,凭着自己对汇编的一点理解就堆出了这些内容,经 egmkang的指点,才发觉自己是井底之蛙,花了半天的功夫,去学习顺序点等内容。针对上次写的程序,我决定添一些内容,把程序2后面的汇编的东西整出来,整理下思路,希望大家看得懂。
下面是第一稿的内容,原封不动
C++中的cout是最常见的,假如cout后面有多个输出的话,他们的输出顺序是什么呢?决定他们输出顺序背后的原理是什么呢?先看下面的代码(1):
#include<iostream> using namespace std; int abs(int a); int main() { ; cout<<a<<endl<<abs(a)<<endl; } int abs(int a) { cout<<"int a"<<endl; ?a:-a); }
大家看一眼第6行的输出结果:很多人认为是-5
int a
5
可实际的结果是这样吗?看下图
图(1)
是不是与我们期望的不一样?
最后的输出结果与cout背后的汇编中堆栈有很大联系,我们知道汇编中堆栈是“后入先出”的存储区,执行第6行语句,从右向左依次把要输出的值压入堆栈,最后从上到下依次输出,第6行的汇编伪代码如下(以下代码不是严格意义上的汇编代码,为容易理解而写成):
push endl;//把换行符压入
push abs(a);//压入函数返回值,函数返回值压入堆栈需要两步:首先先执行cout<<"int a"<<endl;这行语句,故屏幕先显示出int a,然后计算a的绝对值并压入堆栈
push endl;
push a;输出a的值
到此所有的值都压入堆栈,并且此过程中执行其他语句(第9行)的输出结果在屏幕上已经显示出来,然后堆栈中的值依次出栈,在屏幕上先显示-5,再显示5;
假如还有点迷糊的话,再看一个例子(代码2):
#include<iostream> using namespace std; int abs(int a); float abs(float b); int main() { ; float c=-2.4f; cout<<"a="<<abs(a)<<endl<<"c="<<abs(c)<<endl; ; } int abs(int a) { cout<<"int abs"<<endl; ?a:-a); } float abs(float b) { cout<<"float abs"<<endl; ?b:-b); }
假如还按照原来的感觉,那么输出顺序应该是a=int abs
5
c=float abs
2.4
运行结果如下图:
图(2)
还是按照上面提到的汇编的方法分析下,压入堆栈的汇编伪代码如下:
push endl;
push abs(c);//需要强调的是这一步中 float abs立刻在屏幕中显示出来,无需等到所有的值压入堆栈完毕
push c=;
push endl;
push abs(a);//这一步也立刻把int abs显示出来,显示在float abs下面
push a=;
等上面所有的步骤执行完后,然后堆栈中的值依次出栈,最后的输出结果如图(2),代码(2)用到了重载函数,也是很有趣的知识。顺便提一句,在重载函数中,任意两个函数的参数表中函数的个数,各参数的数据类型和顺序不能完全一样,如int func(int a,char b,float c)和double func(int d,char e,float f)。
然后是 egmkang的评论,希望大家要重点看看的:
今天特意反汇编程序2了一下,主要代码及其解释如下,可以结合上面的伪代码看一下(下面附加的解释有些地方可能不是很准确,望包涵):
],0FFFFFFFBh //a=-,把-5这个值放入[ebp-]这个地址单元中 : float c=-.4f; ],0C019999Ah //c=--.4f,把-.4f这个值放入[ebp-]这个地址单元中 : cout<<"a="<<abs(a)<<endl<<"c="<<abs(c)<<endl; (std::endl) (004010c8) //@ILT其实就是一个静态的表,它记录了一些函数的入口然后跳过去, 每个跳转jmp占一个字节,然后就是一个四字节的内存地址,所以加起为五个字节 ] 004015CE push eax //c的值压入堆栈 (abs) ((?abs@@YAMM@Z):004011FE jmp abs (004016e0),float abs的地址正是004016e0 004015D4 fstp dword ptr [esp] //浮点保存出栈 004015D7 push offset string "c=" (0046f020) //"c="这个字符串入栈,其实是这个字符串所在的内存单元的偏移地址入栈 (std::endl) (004010c8) //把(std::endl)的入口地址压栈 ] 004015E4 push ecx //a的值入栈 (_abs) ((_abs): ),int abs的地址正是00401660 004015ED push eax //int abs函数返回的结果放在eax中 004015EE push offset string "a=" (0046f01c) //"a="这个字符串入栈,其实是这个字符串所在的内存单元的偏移地址入栈 004015F3 push offset std::cout (0047ce90) () mov ecx,eax (std::basic_ostream<char,std::char_traits<char> >::operator<<) (004010ff) mov ecx,eax (std::basic_ostream<char,std::char_traits<char> >::operator<<) (004011ea) 0040160E push eax () mov ecx,eax (std::basic_ostream<char,std::char_traits<char> >::operator<<) (0040112c) 0040161E mov ecx,eax (std::basic_ostream<char,std::char_traits<char> >::operator<<) (004011ea) 下面是函数入口的静态表: @ILT+(?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z): 004010C8 jmp std::endl (004026f0) @ILT+(???$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@H@Z): 004010FF jmp std::basic_ostream<char,std::char_traits<char> >::operator<< (004017d0) @ILT+(???$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@M@Z): 0040112C jmp std::basic_ostream<char,std::char_traits<char> >::operator<< (00401a40) @ILT+(_abs): ) @ILT+(???$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z): ) @ILT+(?abs@@YAMM@Z): 004011FE jmp abs (004016e0) @ILT+(??6std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@PBD@Z): jmp std::operator<< (00402d70)
cout<<"a="<<abs(a)<<endl<<"c="<<abs(c)<<endl;这行代码中虽然都调用了abs函数,但是调用的地址是不一样的,abs(c)最后跳到地址为004016e0的地方,而函数float abs(float b)的地址正好是004016e0。abs(a)最后跳到地址为00401660的地方,而函数int abs(int a)的地址正好是00401660。
假如发现了什么问题,大家踊跃评论,我会完善的。
【改进版】C++小程序中一个cout输出语句背后的堆栈知识的更多相关文章
-
微信小程序中的循环遍历问题
比如:如果在微信小程序中要遍历输出 0-9 的数,我们会使用for循环 ;i<;i++){ console.log(i); } 确实结果也是这样: 但是,如果我在循环时同时调用wx的api接口1 ...
-
小程序中,设置Sticky定位,距离上面会有一个缝隙
近日,在小程序中使用sticky定位实现吸顶效果,不料入了一个大坑. 定位后,距离有position: relative:的上级元素有个1px大小的缝隙条,透过缝隙,滑动时可看到定位标题下的内容. 此 ...
-
全栈开发工程师微信小程序-中(中)
全栈开发工程师微信小程序-中(中) 开放能力 open-data 用于展示微信开放的数据 type 开放数据类型 open-gid 当 type="groupName" 时生效, ...
-
两种方法:VS2008下C++窗体程序显示控制台的方法——在QT程序中使用cout和cin
老蔡写了一个基于QT的窗体程序,而过去写的类的调试信息都是用cout显示的,苦于窗体程序无法显示cout信息很多信息都看不到,于是就想到让控制台和窗体同时显示.显示控制台方法如下 1.项目(或者叫“工 ...
-
ES6中Class的用法及在微信小程序中的应用实例
1.ES6的基本用法 ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板.通过class关键字,可以定义类.基本上,ES6 的class可以看作只是一个语法糖,它的绝 ...
-
在小程序中实现 Mixins 方案
摘要: 小程序开发技巧 作者:jrainlau 原文:在小程序中实现 Mixins 方案 Fundebug经授权转载,版权归原作者所有. 在原生开发小程序的过程中,发现有多个页面都使用了几乎完全一样的 ...
-
原生小程序中实现将scss文件实时编译为wxss文件
参考链接 全局安装gulp,方便以后直接执行gulp命令 npm install gulp -g 用原生小程序新建一个项目 在小程序根目录(app.js同级目录)中新建package.json文件 n ...
-
小程序中使用less(最优方式)
写惯了less/sass,但是现在开发小程序缺还是css,很不习惯. 在网上搜的教程,要么是gulp,要么就是vscode的Easy-less的插件. 传统方式 我们来对比,这两种方式的优劣. Gul ...
-
网页或微信小程序中使元素占满整个屏幕高度
在项目中经常要用到一个容器元素占满屏幕高度和宽度,然后再在这个容器元素里放置其他元素. 宽度很简单就是width:100% 但是高度呢,我们知道的是height:100%必须是在父元素的高度给定了的情 ...
随机推荐
-
跟着百度学PHP[6]超级全局变量
超级全局变量在PHP 4.1.0之后被启用, 是PHP系统中自带的变量,在一个脚本的全部作用域中都可用. 参考文献:http://www.runoob.com/php/php-superglobals ...
-
导入项目时,有关[2016-04-03 20:38:02 - Dex Loader] Unable to execute dex: Multiple dex files 问题
最近我在学习androidUI设计,在网上找了一个UI菜单界面开源代码示例,按照步骤导入项目,运行的时候控制台结果报了如下错误: [2016-04-03 20:38:02 - Dex Loader] ...
-
SSIS 文件系统任务无法使用变量配置目标路径
SSIS 文件系统任务无法使用变量配置目标路径 需求: 在SSIS2012中,某个从平面文件导入数据的包中,需要把处理出错的文件拷贝到一个专门的文件夹,便于管理人员及时处理. 问题描述: 1. 在包参 ...
-
ANDROID STUDIO, GRADLE AND NDK INTEGRATION
Originally posted on:http://ph0b.com/android-studio-gradle-and-ndk-integration/ With the recent chan ...
-
MFC圆角背景移动边角底色毛刺解决方案
CRect rc; Graphics graphics(pDC->m_hDC); GetClientRect(&rc); CRgn m_rgn; if (m_pBgImage) { gr ...
-
OpenCV ——背景建模之CodeBook(1)
1,CodeBook算法流程介绍 CodeBook算法的基本思想是得到每个像素的时间序列模型.这种模型能很好地处理时间起伏,缺点是需要消耗大量的内存.CodeBook算法为当前图像的每一个像素建立一个 ...
-
MySql 中文乱码解决办法
mysql存入的中文数据乱码,可能有这两个原因 原因一 : 数据源配置和mysql字符集编码不符,或数据源配置没有设置字符集 解决方案:在数据源配置添加字符集 useUnicode=true& ...
-
Linq 对象的比较 Contains,Max
IList<Student> studentList = new List<Student>() { new Student() { StudentID = 1, Studen ...
-
SpringSocial简介
⒈常用的pom依赖 <dependency> <groupId>org.springframework.social</groupId> <artifactI ...
-
nodejs,mongodb不同时区问题
问题:不同国家,使用不同时区,而服务器代码却在国内,跨时区日期不同,根据日期查询,查询不到数据了 1.mongodb存储的new Date()是UTC时间,也就是0时区的时间,世界标准时间 2.参考m ...