最开始写这篇文章的时候,凭着自己对汇编的一点理解就堆出了这些内容,经 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输出语句背后的堆栈知识的更多相关文章

  1. 微信小程序中的循环遍历问题

    比如:如果在微信小程序中要遍历输出 0-9 的数,我们会使用for循环 ;i<;i++){ console.log(i); } 确实结果也是这样: 但是,如果我在循环时同时调用wx的api接口1 ...

  2. 小程序中,设置Sticky定位,距离上面会有一个缝隙

    近日,在小程序中使用sticky定位实现吸顶效果,不料入了一个大坑. 定位后,距离有position: relative:的上级元素有个1px大小的缝隙条,透过缝隙,滑动时可看到定位标题下的内容. 此 ...

  3. 全栈开发工程师微信小程序-中(中)

    全栈开发工程师微信小程序-中(中) 开放能力 open-data 用于展示微信开放的数据 type 开放数据类型 open-gid 当 type="groupName" 时生效, ...

  4. 两种方法:VS2008下C++窗体程序显示控制台的方法——在QT程序中使用cout和cin

    老蔡写了一个基于QT的窗体程序,而过去写的类的调试信息都是用cout显示的,苦于窗体程序无法显示cout信息很多信息都看不到,于是就想到让控制台和窗体同时显示.显示控制台方法如下 1.项目(或者叫“工 ...

  5. ES6中Class的用法及在微信小程序中的应用实例

    1.ES6的基本用法 ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板.通过class关键字,可以定义类.基本上,ES6 的class可以看作只是一个语法糖,它的绝 ...

  6. 在小程序中实现 Mixins 方案

    摘要: 小程序开发技巧 作者:jrainlau 原文:在小程序中实现 Mixins 方案 Fundebug经授权转载,版权归原作者所有. 在原生开发小程序的过程中,发现有多个页面都使用了几乎完全一样的 ...

  7. 原生小程序中实现将scss文件实时编译为wxss文件

    参考链接 全局安装gulp,方便以后直接执行gulp命令 npm install gulp -g 用原生小程序新建一个项目 在小程序根目录(app.js同级目录)中新建package.json文件 n ...

  8. 小程序中使用less(最优方式)

    写惯了less/sass,但是现在开发小程序缺还是css,很不习惯. 在网上搜的教程,要么是gulp,要么就是vscode的Easy-less的插件. 传统方式 我们来对比,这两种方式的优劣. Gul ...

  9. 在微信小程序中绘制图表(part2)

    本期大纲 1.确定纵坐标的范围并绘制 2.根据真实数据绘制折线 相关阅读:在微信小程序中绘制图表(part1)在微信小程序中绘制图表(part3) 关注我的 github 项目 查看完整代码. 确定纵 ...

随机推荐

  1. Guava 9-I/O

    字节流和字符流 Guava使用术语”流” 来表示可关闭的,并且在底层资源中有位置状态的I/O数据流.术语”字节流”指的是InputStream或OutputStream,”字符流”指的是Reader ...

  2. List集合去重的一种方法 z

    需要对一个List<Model>集合去重,情况是该集合中会出现多个Name属性值相同的,但是其他属性值不同的数据. 在这种情况下,需求要只保留其中一个就好. 我觉得遍历和HashSet都不 ...

  3. 里德九步审讯法 z

    在现实生活中,警方审讯靠的不仅仅是自信和创造力(尽管这两点对审讯工作确有帮助)——审讯者还要在交际影响的心理战术方面接受过高水平训练.       让一个人认罪可不是件容易事,而警察有时能让无辜者承认 ...

  4. python argparse模块解析命令行选项简单使用

    argparse模块的解析命令行选项简单使用 util.py #!/usr/bin/env python # coding=utf-8 import argparse parser = argpars ...

  5. 第2章 Posix IPC

    2.1 概述 Poxix IPC包含:Posix消息队列.Posix信号量.Posix共享内存 2.2 IPC名字 Posix 消息队列.Posix信号量.Posix共享内存这三种Posix IPC都 ...

  6. Play framework(二)

    Play 2.0 的完整演示过程记录 http://www.oschina.net/translate/playframework-20-live-coding-script todolist代码

  7. 算法库:clapack安装配置

    类似于opencv.jpeglib和pnglib的安装配置. opencv安装配置见:http://www.cnblogs.com/dzyBK/p/4954945.html jpeglib和pngli ...

  8. [SQL]select scope_identity()传回插入相同范围之识别资料行中的最后一个识别值

    传回插入相同范围之识别资料行中的最后一个识别值.范围是一个模组:预存程序.触发程序.函数或批次.因此,如果两个陈述式在相同预存程序.函数或批次中,它们就在相同范围中. 语法: SCOPE_IDENTI ...

  9. SparkSQL On Yarn with Hive,操作和访问Hive表

    转载自:http://lxw1234.com/archives/2015/08/466.htm 本文将介绍以yarn-cluster模式运行SparkSQL应用程序,访问和操作Hive中的表,这个和在 ...

  10. Jmeter命令行方式启动

    在性能测试过程中,我们常常遇到这样的问题,使用Jmeter的GUI界面进行大并发量的性能测试时,界面容易卡死,无法继续进行性能测试.通过使用命令行方式启动jmeter是一个不错的方式.下面就简单介绍一 ...