posts - 297,  comments - 15,  trackbacks - 0
在Ailiss社区与人争论语法,终极武器不外乎两把 —— 上天入地。

上天者,搬出枕头厚的大部头引经据点,说有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++协调,很难优化他。

汇编优化可以很快速、很强,但是一定要慎用。
posted on 2008-07-23 16:28 chatler 阅读(131) 评论(0)  编辑 收藏 引用 所属分类: C++_BASIS

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2010年1月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

常用链接

留言簿(10)

随笔分类(307)

随笔档案(297)

algorithm

Books_Free_Online

C++

database

Linux

Linux shell

linux socket

misce

  • cloudward
  • 感觉这个博客还是不错,虽然做的东西和我不大相关,觉得看看还是有好处的

network

OSS

  • Google Android
  • Android is a software stack for mobile devices that includes an operating system, middleware and key applications. This early look at the Android SDK provides the tools and APIs necessary to begin developing applications on the Android platform using the Java programming language.
  • os161 file list

overall

搜索

  •  

最新评论

阅读排行榜

评论排行榜