farseerfc.wordpress.com 导入

C++ Tricks

By  FarseerFc

从今天起,我再将在Live SpaceQQZone同时发表一系列文章,暂定名为“C++Tricks”。

本文旨在记录和阐述一些本人学习C++时所得的心得、技巧。总体来看,本文涉及的内容是每一个C++程序员都应该知道的,但是很少见诸C++教材。希望对各位同仁学习C++有所帮助。

也可以通过QQ或MSN向我索要此文的DOC版或PDF版,会比网页上的更新的快一点。

1      词法问题(Lexical Problems)


2      X86体系结构

     2.1 X86概述

     2.2 …

farseerfc.wordpress.com 导入

2.3 I386平台C函数内部的栈分配

函数使用栈来保存局部变量,传递函数参数。进入函数时,函数在栈上为函数中的变量统一预留栈空间,将esp减去相应字节数。当函数执行流程途径变量声明语句时,如有需要就调用相应构造函数将变量初始化。当执行流程即将离开声明所在代码块时,以初始化的顺序的相反顺序逐一调用析构函数。当执行流程离开函数体时,将esp加上相应字节数,归还栈空间。

为了访问函数变量,必须有方法定位每一个变量。变量相对于栈顶esp的位置在进入函数体时就已确定,但是由于esp会在函数执行期变动,所以将esp的值保存在ebp中,并事先将ebp的值压栈。随后,在函数体中通过ebp减去偏移量来访问变量。以一个最简单的函数为例:

void f()

{

int a=0; //a的地址被分配为ebp-4

char c=1; //c的地址被分配为ebp-8

}

产生的汇编代码为:

push ebp ;将ebp压栈

mov ebp,esp ;ebp=esp 用栈底备份栈顶指针

sub …

farseerfc.wordpress.com 导入

2.4 I386平台C函数调用边界的栈分配

当调用一个函数时,主调函数将参数以声明中相反的顺序压栈,然后将当前的代码执行指针(eip)压栈,然后跳转到被调函数的入口点。在被调函数中,通过将ebp加上一个偏移量来访问函数参数,以声明中的顺序(即压栈的相反顺序)来确定参数偏移量。被调函数返回时,弹出主调函数压在栈中的代码执行指针,跳回主调函数。再由主调函数恢复到调用前的栈。

函数的返回值不同于函数参数,通过寄存器传递。如果返回值类型可以放入32位变量,比如int、short、char、指针等类型,通过eax寄存器传递。如果返回值类型是64位变量,如_int64,同过edx+eax传递,edx存储高32位,eax存储低32位。如果返回值是浮点类型,如float和double,通过专用的浮点数寄存器栈的栈顶返回。如果返回值类型是用户自定义结构,或C++类类型,通过修改函数签名,以引用型参数的形式传回。

同样以最简单的函数为例:

void f(){

int i …

farseerfc.wordpress.com 导入

2.5 I386平台的边界对齐(Align)

首先提问,既然I386上sizeof(int)==4、sizeof(char)==1,那么如下结构(struct)A的sizeof是多少?

struct A{int i;char c;};

答案是sizeof(A)==8……1+5=8?

呵呵,这就是I386上的边界对齐问题。我们知道,I386上有整整4GB的地址空间,不过并不是每一个字节上都可以放置任何东西的。由于内存总线带宽等等的技术原因,很多体系结构都要求内存中的变量被放置于某一个边界的地址上。如果违反这个要求,重则导致停机出错,轻则减慢运行速度。对于I386平台而言,类型为T的变量必须放置在sizeof(T)的整数倍的地址上,char可以随便放置,short必须放在2的整数倍的地址上,int必须放在4的整数倍的地址上,double必须放在8的整数倍的地址上。如果违反边界对齐要求 …

farseerfc.wordpress.com 导入

2.6 I386平台C函数的可变参数表(Variable Arguments)

基于前文(2.4节)分析,我们可以不通过函数签名,直接通过指针运算,来得到函数的参数。由于参数的压栈和弹出操作都由主调函数进行,所以被调函数对于参数的真实数量不需要知晓。因此,函数签名中的变量声明不是必需的。为了支持这种参数使用形式,C语言提供可变参数表。可变参数表的语法形式是在参数表末尾添加三个句点形成的省略号“...”:

void g(int a,char* c,...);

省略号之前的逗号是可选的,并不影响词法语法分析。上面的函数g可以接受2个或2个以上的参数,前两个参数的类型固定,其后的参数类型未知,参数的个数也未知。为了知道参数个数,我们必须通过其他方法,比如通过第一个参数传递:

g(3,”Hello”,2,4,5);//调用g并传递5个参数,其中后3个为可变参数。

在函数的实现代码中,可以通过2.4节叙述的 …

farseerfc.wordpress.com 导入

2.7 I386平台的其它函数调用模型

上文介绍的只是I386平台上C函数调用的标准模型,被称作__cdecl。事实上,Microsoft Visual C++编译器还支持其它一些函数调用模型,所有调用模型名称皆以双下划线开头,下面列出所有函数调用模型的异同:

1 __cdecl

参数压栈顺序:逆序(从右至左)

参数堆栈恢复者:主调函数(caller)

__cdecl明确地指出函数使用C函数调用模型,这是默认的调用模型。

2 __stdcall

参数压栈顺序:逆序(从右至左)

参数堆栈恢复者:被调函数(callee)

__stdcall是微软所谓的标准调用模型。可惜的是它与__cdecl不兼容。几乎所有的Win32API函数使用这种函数调用模型,希望在DLL之间,或者在程序和WinNT操作系统之间传递函数指针的函数也应该使用这种模型。与__cdecl模型的不同之处在于,__stdcall模型下由被调函数恢复堆栈。主调函数在call语句之后,不需要再加上add语句。而被调函数的ret语句则被添加一个参数,代表函数参数堆栈的长度。因此,被调函数需要明确的知晓函数参数的数量和类型,所以在__stdcall模型下不支持可变参数表,所有参数必须写明 …

farseerfc.wordpress.com 导入

2.1   X86概述

所谓X86体系结构,是指以Intel 8086芯片为首的芯片所沿袭的CPU结构,一些文档中又被称作IA32体系结构。包括的芯片有但不限于:Intel 8086至 80486,奔腾(Pentium)系列处理器1至4,赛扬系列处理器,酷睿系列处理器,以及AMD的相应型号产品。X86体系结构在早期属于16位处理器,自80386之后扩展为32位处理器,所以一些文档中又把80386之后的32位处理器体系称作I386。自Pentium4后期,AMD的Athlon64开始,I386被进一步扩充为64位处理器,含有64位寻址能力的X86体系结构被称作X86-64或IA32-64。总之,市售的个人电脑用CPU,除苹果的Macintosh之外,全部采用X86体系结构芯片。

在X86早期,16位的寻址能力只支持64KB(2^16=64K)内存,这显然是不够的。Intel采用分段寻址的方法,用4位段位+16位偏移量,提供了总共1MB(2^20=1M)的寻址能力。所以在X86的16位编程中,有两种指针类型 …

farseerfc.wordpress.com 导入

1.2   逗号运算符(,)、逻辑运算符(&&,||)与运算符重载的陷阱

很多人甚至不知道逗号(,)也是个C++运算符。与语法上要求出现的逗号(比如分隔函数参数的逗号)不同的是,出现在表达式中的逗号运算符在语义上表示多个表达式操作的连续执行,类似于分隔多语句的分号。比如:

for(inti=0,j=9;i<10;++i,--j)std::cout<<i<<”+”<<j<<”=9\n”;

在这句语句中,出现了两个逗号,其中前者是语法上用来分隔声明的变量的,并非逗号运算符,而后者则是一个逗号运算符。根据C++标准,逗号运算符的执行顺序为从左到右依次执行,返回最后一个子表达式的结果。由于只有最后一个表达式返回结果,所以对于一个语义正常的逗号表达式而言,前几个子表达式必须具有副作用。同时,从语言的定义中也可以看出,逗号表达式对求值的顺序有严格要求 …

farseerfc.wordpress.com 导入

1.1   条件运算符(?:)

条件运算符(?:)是C++中唯一的三目运算符(trinary operator),用于在表达式中作条件判断,通常可以替换if语句,与Visual Basic中的iif函数、Excel中的if函数有同样的作用。语法形式如下:

condition ? true_value : false_value

其中condition *条件是任何可以转换为bool类型的表达式,包括但不仅限于**bool*int、指针。与ifwhile的条件部分稍显不同的是,这里不能定义变量,否则会导致语法错误。

另外,条件语句会切实地控制执行流程,而不仅仅是控制返回值。也就是说,两个返回值表达式中永远只有一个会被求值,在表达式的执行顺序很重要时,这点尤为值得注意。比如:

int *pi=getInt();

int i=pi …

farseerfc.wordpress.com 导入

填补信仰、唤醒良知

我们听尽了呼吁与号召,对于良知,我不必谴责丧失它的国人,不必盛赞良知的美好。我只想讨论,丧失了良知的原因——空缺的信仰。

一、空缺信仰丧失良知

现代的国人缺少信仰,以至于丧失良知。曾几何时,中华民族由良好的信仰凝聚而成。三皇五帝时,族民们以炎黄为信仰;春秋战国时,士大夫之族以周制礼乐为信仰;汉代以后,百姓延习孔孟之说、老聃之道,以儒家学说为信仰;自大唐起,以佛教为首的现代宗教纷纷传入中原,人民开始以它们作为信仰。

直至鸦片战争、五四运动,西方文化入侵中华,国人开始抛弃国学,转而去研究科学;文化大革命,十年文化浩劫,人们批判旧的信仰,却没有合适的新的信仰前来填补。从此,国人的信仰出现空缺,国人的良知也被一块块蚕食殆尽。

二、信仰、科学、迷信

在许多国人的心目中,信仰就等于迷信。从小到大的教育告诉我们 …