VC7 中汇编和C++混合的初步心得

CSDN社区与人争论语法,终极武器不外乎两把 —— 上天入地。

上天者,搬出枕头厚的大部头引经据点,说有Lipman某典故云云;又有C++标准M页N条款如是说...

入地者,操起起子扳手把程序拆个凄凉八落,啪啪啪回上一大片编译器汇编的输出,说——看吧,都在这里了。

事实上,我发觉两者对于深入理解C++都是必不可少的。前些时,论坛上突然流行讨论数组的本质。怪的是,每次我以为自己真的懂了,下一次又却发现自己的轻浮。等到通晓实现细节,再回过头来看最土的C教材的定义,竟发现字字玑珠。

最近对入地颇感兴趣,动辄查汇编,写了不少混合代码研究语法。放下扳手抹抹鼻尖上的机油,倒是有些心得。各位看官——我就要跟大家乱侃近来入地一个多月,邪门歪地道挖出杂七杂八的东西——的工具——就 汇编。

——喝口茶先,大家先看看秋镇菜blog上这篇文章 Visual C++ 中使用内联汇》 ,详细介绍了在 Visual C++ 中内联汇编的用法。

参考书也必不可少;可悲的是我手头仅有的两本书一本是老掉牙的8086汇编,另一本则是AT&T语法的 —— 广泛用于Linux上的编译器, 但VC 偏要使用Inter语法.... 带来麻烦不少,希望大家慷慨解囊之前先看准。

 


 1 察看编译器输出

常来说,Debug 模式单步跟踪时Alt+8 就可以看见汇编代码。问题是 Debug 只是代表了一个侧面,并不代表最终的 Release ;另一方面 Debug 模式包含了些许额外的测试代码 —— 恩,可能代码有些多...天啊,他们干嘛要加、那么多、莫名其妙的代码混淆视听阿!

好嘛,看看简洁的Release模式 —— orz.... 不能单步跟踪C++程序了? 连main函数在哪里都看不见... 瞎了...

Release 模式单步跟踪要需要高深的技术底气。不过也没那么绝,要看 Release 模式的输出,我们可以在项目属性->C/C++->输出文件页面中把“汇编输出”项定为“带源代码的程序集(/FAs)”。这样,在Release目录下就可以看见对应的asm文件了。看asm文件,唯一的缺点是不能单步跟踪研究。

这个asm文件搞不好会非常大——主要是由于C++标准库广泛使用模板的原因,若我们放弃C++库一律使用C标准库就会看到很干净的asm文件(同时会看见一个1/4大小的可执行文件,你会明白为什么那么多人支持C )——当然这不是C++的干活。 要在这个动辄数W行的文件中里面找源代码对应的汇编,推荐大家找一行一定不会被优化掉的代码(没错,某些代码可能人间蒸发),直接F3搜索。

asm中包含了很多注释,有基本的汇编知识然后连蒙带猜就能看懂了。一对挺有用的标志是:

_TEXT SEGMENT   // 代码段开始标志
_TEXT ENDS      // 代码段结束标志

对于观察每个函数的生成代码来说,这两个标志能起到路标的作用。


 2 汇编访问类成员

有一个类

class A{
  int _i;  
};

有A 的实例a,下面的代码令 a._i  = 10,这只需要一个指令:

__asm mov [a]A._i, 10

但是在A 的成员函数中怎么办呢?

我们知道,成员函数调用为 thiscall, this通过 ecx传递。所以在函数的开头现场尚未被破坏的时候,可以直接用 ecx 变址访问。如下面是一个常见的set函数, 它令 A::_i = n (注意mov等指令中,两个操作数不能同时为内存内容,所以必须用寄存器eax接力):

inline void A::i( int n ){
  __asm mov eax, n
  __asm mov [ecx]A._i, eax
}

不过这有两个问题。一来,ecx并非总是this;它随时可能被刷掉。在某个不能确定保存this寄存器的时候,你需要手动写ecx:

__asm mov ecx, this
__asm mov eax, n
__asm mov [ecx]A._i, eax

这样写会迫使编译器把this的值复制到栈上 —— 而一般来说对于小函数而言,编译器会尽量只用寄存器。这可能是一个额外的小小开销。(注意,千万不要以为可以这么访问:  [this]A._i  )

另一方面,虽然在我们的确写了大大的“ inline ”几个字,但是看看输出代码——你会发现:任何包含了内嵌汇编的 inline 成员函数都不会被内联!


3 汇编/内联函数和效率

 

通函数是可以内联的,下面就是一个完美的结合 C++/ asm 的例子:

inline long long getTimer(){
  long long time;
  __asm rdtsc
  __asm mov DWORD PTR time, eax
  __asm mov DWORD PTR time + 4, edx
  return time;
}

rdtsc指令用来获得CPU自开机运行的时钟周期数。它的结果是64位的,保存到 eax 和 edx两个寄存器中,可以用来精确测量算法开销。上面的函数内联之后, 局部变量不见了, 临时返回值也不见了,只有最核心的三行代码,没有比这更简洁的了:

; 68   :  long long b = getTimer();

rdtsc
mov DWORD PTR _time$11298[ebp], eax
mov DWORD PTR _time$11298[ebp+4], edx

成员函数内联则又是另一个故事:系统不知道如何处理this,所以他干脆忽略所有内嵌asm成员函数的内联标志。

好嘛,VC不愿上,我们用皮鞭赶着他上! 把第二部分最初那个 A::i 改为 __forceinline 就强制内联了——也就是强制VC犯错误了:不幸的编译器看不懂我们的代码,只好把指令抄到函数调用处。他不晓得初始化ecx,那个mov可能往任何地方写内容——比如把你的开机密码写到桌面上——

虽然可以手动设置ecx,不过我们可不希望看见如此丑陋的调用(想象一下你的同事看到这段代码的困惑):

__asm lea ecx, a 
a.i( 20 );

要正确编写能成功内联的代码必须结合另一个方案,手动复制this:

__forceinline void A::i( int n ){
  __asm mov ecx, this
  __asm mov eax, n
  __asm mov [ecx]A._i, eax
}

厄。。。猜猜看结果如何?

首先看看我们直接用C++写一个 set函数 (譬如 void A::i( int n ){  _i = n; } )内联后的结果:

; 56   :  a.i( 5 );

mov DWORD PTR _a$[ebp+8], 5

最残酷的结果也只需一句mov。 更可能的结果是——他被优化得连影儿都看不见。
然后看看我们的三年怀胎含辛茹苦研究出来的混合汇编的内联:

; 56   :  a.i( 5 );

 lea eax, DWORD PTR _a$[ebp]
 pop ecx
 mov DWORD PTR $T11194[ebp], eax
 mov ecx, DWORD PTR $T11194[ebp]
 mov eax, 5
 mov DWORD PTR [ecx+8], eax

这么长啊....生出一个怪胎... VC 中嵌入汇编的一个坏处是:编译器很难将他和C++协调,很难优化他。

汇编优化可以很快速、很强,但是一定要慎用。

一、 实验目的和要求 掌握VC++语言和汇编语言的混合编程方法,了解不同编程语言的接口方法,体会汇编语言的应用。 掌握嵌入汇编函数和汇编语言子程序与VC++混合编程方法,入口、出口参数的传递方法以及在VC++环境下混合编程的调试方法。 二、 实验条件 硬件:计算机一台 软件:Visual Studio C++ 6.0、MASM 6.0 三、 实验原理分析 在Turbo C++或Borland C++编程环境下,我们可TCC或BCC行命令把一个C语言的源程序转换成汇编语言的源程序。通过阅读汇编语言程序可以很准确地知道C语言语句的功能是如何实现的。 C语言源程序转换的命令格式如下: TCC -S t1.c 或 BCC -S t1.c   ;假设其文件名为t1.c 注意:(1)TCC在TC目录下,若命令TCC/BCC不带参数的话,则将显示其使用方法。 (2)其-S要求S为大写。 (3)在TC上做以上操作,必须保证:TC正常安装(c:\turboc2),目录名及文件夹名都不能改变。 下面是C语言程序及其相对应的汇编语言程序,希望读者能逐行对照理解它们语句之间的转换关系,这将能进一步理解高级语言的语句功能。 (4) 汇编语言和高级语言混合编程,需要解决两个主要的技术问题: 不同语言程序模块之间的连接; 调用过程参数的传递方法。 对此不同的高级语言或同一种高级语言的不同版本所采取的具体方法不尽相同。本节主要介绍汇编语言与C/C++语言接口的基本方法。 调用协议是指在进行子程序调用时,主程序向子程序传递参数以及从子程序获得返回值的约定方式。 通常参数传递的方法是:主程序使用系统堆栈向子程序传递入口参数,子程序使用CPU内部寄存器来保存向主程序的返回值。此外调用协议还将确定哪些寄存器的内容需要保护,哪些寄存器可以自由使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值