- 4.1 Member的各种调用方式
- 4.2 Virtuar Member Functions(虚拟成员函数)
- 4.3 函数的效能
- 4.4 指向Member Function的指针(Pointer-toMember Functions)
- 4.5 Inline Functions
- 看以下代码,会发生什么: 答案是不知道。C++支持三种类型的member function:static、nonstatic和virtual,我们蹦确定normalize()和magnitude()两函数是否为virtual或nonvirtual,但可以确定它不是static。因为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20Point3d obj;
POint3d *ptr = &obj;
obj.normalize();
ptr->normalize();
// declaretion
Point3d Point3d::normalie() const
{
register float mag = magnitude();
Point3d normal;
normal._x = _x/mag;
normal._y = _y/mag;
normal._z = _z/mag;
return normal;
}
float Point3d::magnitude() const
{
return sqrt(_x * _x + _y * _y + _z * _z);
}- 它直接存取nonstatic数据。
- 它被声明为const。是的,static member functions不可能做到这两点。
4.1 Member的各种调用方式
原始的“C with Class”只支持nonstaic member functions。它收到很多质疑:
有一种常见的观点,认为virtual functions只不过是一种蹩脚的函数指针,没有什么用……其意思主要就是,virtual functions是一种没有效能的形式。
Static member functions是最后被引入的一种函数类型。
Nonstatic Member Functions(非静态成员函数)
- C++的设计准则之一就是:static member function至少必须和一般的nonmember function有相同的效率。然而,举个例子: 乍见似乎member function比较没有效率,它经由参数取坐标,而member function却直截了当用坐标成员。然而实际上member function被内化为nonmember的形式。下面是转化步骤:
1
2
3
4
5
6// nonmember
float magnitude3d(const Point3d *_this) {
return sqrt(_this->_x * _this->_x +
_this->_y * _this->_y +
_this->_z * _this->_z);
}- 改写函数的signature(函数原型)以安插要给额外的参数到member function中,用以提供一个存取管道,使class object将此函数调用。这个额外的参数使this指针:
1
2
3
4
5// non-const nonstatic member
Point3d Point3d::magnitude(Point3d *const this)
// 如果function是const
// const nonstatic member
Point3d Point3d::magnitude(const Point3d *const this) - 将每一个对nonstatic data member的存取操作变为经由this指针来存取:
1
2
3
4
5{
return sqrt(this->_x * this->_x +
this->_y * this->_y +
this->_z * this->_z);
} - 将member function重新写成一个外部函数。函数名经过“mangling”处理,使它在程序中成为独一无二的词汇:
1
extern magnitude__7Point3dFv(register Point3d *const this);
- 改写函数的signature(函数原型)以安插要给额外的参数到member function中,用以提供一个存取管道,使class object将此函数调用。这个额外的参数使this指针:
- 开章的normalize()函数会转化成以下形式,假设声明了Pointe3d copy constructor,而named returned value(NRV)的优化也施行了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// C++伪码
void normalize__7Point3dFv(register const POint3d *const this, Point3d &__result)
{
register float mag = this->magniude();
// default constructor
__result.Point3d::Point3d();
__result._x = this->_x/mag;
__result._y = this->_y/mag;
__result._z = this->_z/mag;
return;
}
// 有效率的方式
Pointe3d Point3d::normalize() const
{
register float mag = magnitude();
return Point3d(_x/mag, _y/mag, _z/mag);
}
// 转化
void normalize__7Point3dFv(register const Point3d *const this, Point3d &__result)
{
register float mag = this->magnitude();
// __result return value
__result.Point3d::Point3d(this->_x/mag, this->_y/mag, this->_z/mag);
return;
}
名称的特殊处理(Name Mangling)
- 一般而言,member的名称前面会被加上class名称,形成独一无二的命名: 为什么这么做呢?考虑这样的派生操作:
1
2
3class Bar { public: int ival; ... };
// ival可能为
// ival__3Bar不管要处理哪个ival,通过name mangling都能指出来。由于member functions可能被重载(overloaded),所以更需要广泛的mangling手法:1
2
3
4
5
6
7class Foo : public Bar { public: int ival; ... };
// Foo对象内部结合了base class和derived class两者
class Foo {
public:
int ival__3Bar;
int ival__3Foo;
}1
2
3
4
5
6
7
8
9
10
11
12class Point {
public:
void x(float newX);
float x();
...
}
// 转化为:
class Point {
public:
void X__5PointFf(float newX);
float X__5PointFv();
} - 把参数和函数名称编码在一起,编译器就在不同的编译模块之间达成了一种有限形式的类型检验。如果两个实例拥有独一无二的name mangling,任何不正确的调用操作在链接时会因无法resolved而失败。这就是确保类型安全的链接行为(type-safe linkage)。它只能捕捉到函数的标记(signature,函数名称+参数个数+参数类型)错误,而返回类型错误检查不出来。而一种demangling工具,用来拦截名称并转换回去。它向使用者隐藏了内部名称,使得出错时能得到一个友好的信息返回。
Virtual Member Functions(虚拟成员函数)
- 如果normalize()是virtual member function,那么以下调用: 会被转化为:
1
ptr->normalie();
其中:1
(*ptr->vptr[1])(ptr);
- vptr由编译器产生,一个指向vtbl的指针。
- 1是索引值。
- ptr是this指针。
- 类似道理,如果magnitude()也是virtual function,它在normalize()之中的调用操作被转换如下: 此时,由于Point3d::magnitude()是在Point3d::normalize()中被调用的,而后者已经由虚拟机之而决议(reslove)妥当,显式的调用Point3d实例会比较有效率,并因此压制由于虚拟机之而产生的不必要重复调用操作:
1
2// register float mag = magnitude();
register float mag = (*this->vptr[2])(this);如果magnitude()声明为inline函数,会更有效率。因为使用class scope operator显式调用virtual function,resolve方式和nonstatic member function一样:1
2// explicitly invocation
register float mag = Point3d::magnitude();这时对于以下调用:1
register float mag = magnitude__7Point3dFv(this);
如果编译器转化为:1
2// Point3d obj;
obj.normalize();虽然语义正确,却没有必要。所以上述经由obj调用的函数实例只可以是Point3d:normalize()。”经由class object调用virtual function”,这种操作总是被编译器像对待一般nonstatic member function一样resolved:1
(*obj.vptr[1])(&obj);
这项工程的另一个利益是,virtual function的inline函数实例可以被扩展(expanded)开来,因而提供极大的效率利益。1
normalize__7Point3Fv(&obj);
Static Member Functions(静态成员函数)
- 如果Point3d::normalize()是一个static member function,下面两个调用的转化: 在引进static member functions之前,你很少看到这种怪异写法:
1
2
3
4
5
6
7obj.normalize();
ptr->normalize();
// 将被转化为一般的nonmember函数调用
// obj.normalize();
normalize__7Point3dSFv();
// ptr->nomalize();
normalize__7Point3dSFv();在引进static member functions之前,C++语言要求member functions必须经由class的object来调用。而实际上,只有当一个或多个nonstatic data members在member function中被直接存取时,才需要class object。如果没有任何一个members被直接存取,就不需要this指针,也就不需要通过一个class object来调用member function。1
((Point3d*)0)->object_count();
这一来存取static data members时产生了一些不规则性。如果class的static data member声明为nonpublic,那么就必须提供member functions来存取member。这时很显然,虽然补考class object来存取static member,但存取函数却得绑定一个class object上。
独立于class object之外的存取操作很重要,尤其在没有class object存在的情况。程序方法上的解决之道是把0强制转换为一个class指针:至于语言层面上的解决之道,是引进的static member functions。它的主要特性是没有this指针。以下是次要特性:1
object_count((Point3d*)0);
- 它不能直接存取class中的nonstatic members。
- 它不能被声明为const、volatile或virtual。
- 它不需要经由class object才被调用——虽然大部分时候它是这样被调用的。
- 如果去一个static member funciton的地址,获得的将是在内存中的位置,也就是其地址。由于static member function没有this指针,所以地址类型不是一个指向class member funciton的指针,而是一个nonmember函数指针: 因为static member function缺乏this指针,所以差不多等同于nonmember function。它有个意想不到的好处:成为callback函数。
1
2
3
4
5&Point3d::object_count();
// 会得到数值,类型是
unsigned int (*)();
// 而不是
unsigned int (Point3d::*)();
4.2 Virtuar Member Functions(虚拟成员函数)
我们已经知道了virtual function的一般实现模型:每个class中有个vtbl,其中包含着virtual function的地址,而每个object有vptr,指向vtbl的存在。
为了支持virtual function机制,对于多态对象必须有某种形式上的执行期类型判断法(runtime type resolution)。也就是下面调用需要ptr在执行期有某些信息,才能找到并调用z():
1
ptr->z();
这份信息不能加在ptr身上,它增加了空间负担,即使不使用多态;第二它打断了与C程序间的链接兼容性。我们需要一个更好的规范,一个以class的使用为基础,而不在乎关键词是class或struct的规范。并且必须在执行期多态(runtime polymorphism)时才需要这份信息
C++中多态表示以一个public base class的指针(reference),寻址出一个derived class object的意思。经由指针,可以在程序中任何地方采用public derived类型,这种多态形式是消极的(passive),可以在编译期完成,除了virtual base class的情况。当指出的对象被使用时,才变成积极的(active)。
在runtime type identification(RTTI)性质在1993年被引入前,C++对于积极多态(active polymorphsim)的唯一支持,就是对virtual function call的resolution操作。有了RTTI,能够在执行期查询一个多态的pointer或多态的reference了:
1
2
3
4
5// 积极多态的例子
ptr->z();
//
if (Point3d *p3d = dynamic_cast<Point3d*>(ptr))
return p3d->_z;z()是一个virtula function。是什么信息让我们在执行期调用正确呢?,我们需要知道:
- ptr所指对象的真实类型。
- z()实例的位置,以便能够调用它。
实现上,可以在class object身上增加两个members:
- 一个字符串或数字,表示class的类型;
- 一个指针。指向表格,表格中有个virtual function的执行期地址。
表格中的virtual functions地址是怎么建构起来的呢?virtual function(可由object被调用)在编译期获知。这个地址是固定的,执行期不增添也不替换。完全由编译器掌控,不需要执行期介入。而执行期只是备妥了函数地址,并未被找到。找到那些地址。两个步骤完成任务:
- 为了找到表格,class object被安插了编译器内部产生的指针,指向该表格。
- 为了找到函数地址,每个virtual function被指派为一个表格索引值。
以上的工作都由编译器完成。执行期要做的,只是在特定的virtual table slot中激活virtual funciton。
一个class只有一个vtbl。每个table内含对应object的active virtual functions函数实例的地址,这些active virtual functions包括:
- class所定义的函数实例。它会overriding存在的base class virtual function函数实例。
- 继承自base class的函数实例。这是在derived class决定不改写virtual function时才出现的情况。
- 一个pure_virtual_called()函数实例。
每个virtula function都被指派一个固定的索引值:
1
2
3
4
5
6
7
8
9
10
11
12
13class Point {
public:
virtual ~Point();
virtual Point& mult(float) = 0; // puree virtual function
// ...
float x() const { return _x; }
virtual float y() const { return 0; }
virtual float z() const { return 0; }
// ...
protected:
Point(float x = 0.0);
float _x;
};virtual destructor被指派slot 1,而mult()被指派slot2,y()被指派slot3,z()被指派slot4。在单一继承体系中,virtual
fucntion机制行为十分良好,有效率且容易塑造出模型。而在多重继承或虚继承中,对virtual functinos的支持就没那么好了。
多重继承下的Virtual Functions
多重继承中支持virtual functions,复杂度围绕在第二个及后继base class身上,以及”必须在执行期调整this指针“这一点:
1
2
3graph BT
Derived -- public --- Base1
Derived -- public --- Base21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28class Base1 {
public:
Base1();
virtula ~Base1();
virtual void speakClearly();
virtual Base1* clone() const;
protected:
float data_Base1;
};
class Base2 {
public:
Base2();
virtual ~Base2();
virtual void mumble();
virtual Base2* clone() const;
protected:
float data_Base2;
};
class Derived : public Base1, public Base2 {
public:
Derived();
virtual ~Derived();
virtual Derived* clone() const;
protected:
float data_Derived;
};它的困难度主要体现在Base2 subobject身上,有三个问题需要解决:*(1)virtual destructor*,(2)被继承的Base2::mumble(),(3)clone()函数实例。
1
2
3
4
5Base2 *pbase2 = nwe Derived;
// 编译时期代码
// 转移以支持第二个base class
Derived *temp = new Derived;
Base2 *pbase2 = temp ? temp + sizeof(Base1) : 0;如果没有这样的调整,任何非多态的应用都将失败:
1
2
3
4
5// pbase2被指定一个Derived对象,这也应该没有问题
pbase2->data_Base2;
// 当程序员要删除pbase2所指的对象时
// 需要调用正确的virtual destructor函数实例,pbase2可能需要调整
delete pbase2;一般规则时,经由第二或后继base class的指针(reference)来调用derived class virtual funciton。其所连带的必要this指针调整操作,必须在执行期完成。也就是offset的大小,offset加到this指针上的程序代码,必须由编译器在某个地方插入。
cfont编译器中的方法是将vtbl加大,容纳所需的this指针,每个virtual table slot不再是指针,而是一个集合体,内含offset以及地址:
1
2
3(*pbase2->vptr[1])(pbase2);
// 改变为
(*pbase2->vptr[1].faddr)(pbase2 + pbase2->vptr[1].offset);其中faddr内含virtual function地址,offset内含this指针调整值。这个做法的缺点是,不管什么virtual function都得进行offset调整。
比较有效率的解决方法是利用所谓的thunk(是一段assembly代码):(1)适当的offset调整this指针,(2)跳到virtual function去,如:1
2
3pbase2_dtor_thunk:
this += sizeof(base1);
Derived::~Derived(this);Thunk技术允许vtbl slot继续内含简单指针,因此空间上不需要任何负担。slots的地址直接指向virtual function,也可以指向相关的thunk。
调整this指针的的第二个额外负担就是,由于(1)经由derived class(或base class)调用,(2)经由第二(或后继)base class调用,同一函数在vtbl可能需要多次对应的slots:
1
2
3
4Base1 *pbase1 = new Derived;
Base2 *pbase2 = new Derived;
delete pbase1;
delete pbase2;两个相同的derived destructor,但需要不同的vtbl slots:
- pbase1不需要调整this指针。vtbl slot需放置真正destructor地址。
- pbase2需要调整this指针。vtbl slot需相关的thunk地址。
多重继承下,derived class内含n-1个额外的vtbl,n表示上一层base classes的个数。对于本例的Derived而言,会有两个vtbl被编译器产生出来:
- 一个主要实例,与Base1共享。
- 一个次要实例,与Base2有关。
用以支持一个clas拥有多个vtbl的传统方法是,将每个tables以外部对象的形式产生出来,并给予独一无二的名称:
1
2vtbl__Derived; // 主
vtbl__Base2__Derived; // 次于是当你将Derived对象地址指定给Base1指针或Derived指针时,被处理的vtbl时主要表格vtbl__Derived。当讲Derived对下给你地址指定给Base2指针时,被处理的vtbl时次要表格vtbl__Base2_Derived。
由于执行期链接器(runime linkers)的降临(可以支持动态共享函数库),符号名称的链接可能变得非常缓慢。为了调节执行期编译器的效率,Sun编译器将多个vtbl连锁为一个:指向次要表格的指针,可由主要表格表格名称加上一个offset获得,这样的策略下,每个class只有一个具名的vtbl。
有三种情况,第二或后继的base class会影响virtual functions的支持。
- 通过一个指向第二个base class的指针,调用derived class virtual function。
- 通过一个指向derived class的指针,调用第二个base class中的一个继承而来的virtual funciton。
- 允许一个virtual function的返回值类型有所变化,可能是base type,也可能是publicly derived type。这一点经由Derived::clone()函数实例来说明。clone函数的Derived版本传回一个Derived class指针,默默地改写了它们两个base class函数实例。当通过指向第二个base class的指针来调用clone()时,this指针的offset问题诞生了:
1
2
3
4Base2 *pb1 = new Derived;
// 调用Derived* Derived::clone()
// 返回值必须被调整,以指向Base2 subobject
Base2 *pb2 = pb1->clone();
当函数被认为足够小的时候,Sun编译器会提供一个split functions技术,以相同的算法产生两个函数。这样不论通过Base1指针还是通过Derived指针调用函数,都不需要调整返回值,而通过Base指针所调用的时另一个函数,并在返回前,为指针加上必要的offset。如果函数不小,会给函数中其中一个进入点,进入点需要三个指令。
thunk则是函数一开始先(1)调整this指针,然后才(2)执行程序员所写的函数码;无需调整函数调用操作。
Microsoft用adderss points来取代thunk策略,即overriding function期待获得的是引入该virtual function的class的地址,这就是函数的address point。
虚拟继承下的Virtual Functinos
- 考虑下面的virtual base class派生体系:
1
2graph BT
Point2d --- Point3d当一个virtual base class从另一个virtual base class派生而来,并且两者都支持virtual functions和nonstatic data members时,编译器对于virtual base class的支持简直就像进了了迷宫一样。所以建议是,不要再一个virtual base class中声明nonstatic data members。如果这么做,你会距离复杂的深渊愈来愈近。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Point2d {
public:
Point2d(float = 0.0, float = 0.0);
virtual ~Point2d();
virtual void mumble();
virtual float z();
// ...
protected:
float _x, _y;
};
class Point3d : public virtula Point2d {
public:
Point3d(float = 0.0, float = 0.0, float = 0.0);
~Point3d();
float z();
protected:
float _z;
};
4.3 函数的效能
- 下面这组测试,在编译器上计算两个3D点,其中用到一个nonmember friend function,一个member function,以及一个virtual member function。p170
4.4 指向Member Function的指针(Pointer-toMember Functions)
- 去一个nonstatic data member 的地址,得到的结果是该member在class布局中的bytes位置(再+1)。可以想象它是一个不完整的值,它需要被绑定于某个class object的地址上,才能够被存取。
取一个nonstatic member function的地址,如果该函数是nonvirtual,得到的结果是它在内存中真正的地址。然而也是不完全的,也需要绑定于某个class object的地址上,才能够通过它调用函数。回顾一下:指向member function的指针的声明语法,以及指向member selection运算符的指针,作用是作为this指针的空间保留着。这就是为什么static member function(没有this指针)的类型是”函数指针“,而不是指向member function的指针的原因。 使用一个member function指针,如果并不用于virtual functions、virtual base class或multiple base classes等情况的画,并不会比使用一个nonmember function指针的成本更高。上述三种情况对于member function指针的类型及调用都太过复杂。对于没有以上情况的class而言,编译器可以为它们提供相同的效率。1
2
3
4
5
6
7
8
9
10double (Point::*pmf)();
// 定义
double (Point::*coord)() = &Point::x;
coord = &Point::y;
// 调用
(origin.*coord)();
(ptr->*coord)();
// 转化
(coord)(&origin);
(coord(ptr);
支持“指向Virtual Member Functions”的指针
- 考虑下面程序片段: pmf,一个指向member function的指针,被设值为Point::z()(一个virtual function)的地址。如果由ptr调用z(),被调用的是Point3d::z(),但如果从pmf间接调用z(),正常运行吗?yes。
1
2float (Point::*pmf)() = &Point::z;
Point *ptr = new Point3d;
对于一个virtual function取其地址,所能获得的只是一个索引值。对于一个指向member function的指针评估求值(evaluated),会因为该值由两种意义而复杂化,调用也有别于常规,float (Point::*pmf)();
必须允许此函数能够寻址出nonvirtual x()和virtual z()两个member functions:只不过其中一个代表内存地址,另一个代表vtbl中的索引值。为了使pmf能够(1)持有两种数值,(2)能区别代表内存地址还是vtbl中的索引值:1
2
3// 两者都可以指定给pmf
float Point::x() { return _x; }
float Point::z() { return 0; }1
2
3// true is non-virtual invocation
// false is virtual invocation
(((int)pmf) & ~127) ? (*pmf)(ptr) : (*ptr->vptr[(int)pmf](ptr));
在多重继承之下,指向Member Functions的指针
- 为了让member functions的指针能够支持多重继承和虚拟继承,设计了一个结构体: index和faddr分别持有vtbl slot和nonvirtual member function address(当index不指向vtbl时,设为-1),像以下调用:
1
2
3
4
5
6
7
8struct __mptr {
int delta;
int index;
union {
ptrtofunc faddr;
int v_offset;
};
};这个方法受到的批评是,每个调用操作都得付出成本。Microsoft把检查拿掉,导入一个vcall thunk,它会选出并调用相关vtbl中的slot。 这个结构体的另一个副作用是,当传递一个不变值的指针给member function时,需要产生临时变量:1
2
3
4
5
6
7(ptr->*pmf)();
// 变成
(pmf.index < 0)
? // non-virtual invocation
(*pm.faddr)(ptr)
: // virtual invocation
(*ptr->vptr[pmf.index](ptr));回到开头那个结构体。delta字段表示this指针的offset值,而v_offset字段放的是一个virtual base class的vptr位置。如果vptr被编译器放在class对象起头处,这个字段就没必要了。它只在多重继承或虚拟继承情况下才有必然性。有些编译器根据不同的class特征提供多种memer functions的指向方式。如Microsoft:1
2
3
4
5
6
7
8
9
10
11extern Point3d foo(const Point3d&, Point3d (Point3d::*)());
void bar(const Point3d &p) {
Point3d pt = foo(p, &Point3d::normal);
// ...
}
// &Point3d::normal value
{0, -1, 10727417}
// 将变成
__mptr temp = {0, -1, 10727417}
foo(p, temp);- 单一继承实例(有vcall thnuk地址或是函数地址);
- 多重继承实例(有faddr和delta两个members);
- 虚拟继承实例(4个members)。
”指向Member Functions之指针的效率
- 又是测试。p180
4.5 Inline Functions
一个Point class的加法运算符的可能实现内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Point {
friend oint operator+(const Point&, const Point&);
}
Point operator+(const Point &lhs, const Point &rhs)
{
Point new_pt;
new_pt._x = lhs._x + rhs._x;
new_pt._y = lhs._y + rhs._y;
return new_pt;
}
// void Point::x(float new_x) { _x = new_x; }
// float Point::x() { return _x; }
new_pt.x(lhs.x() + rhs.x());以上通过将存取函数声明为inline,不但可以保持直接存取的高效率,而且加法运算符不再需要被声明为Point的friend。
然而不能够强迫任何函数都变成inline。关键词inline(或者class declaration中的member function或friend function的定义)只是一项请求。如果请求被接受,编译器就认为它可以用一个表达式(expression)合理地将这个函数扩展开来。cfront有一套复杂的测试方法,通常是用来计算assignments、function calls、virtual function calls等操作的次数。每个expression种类都有一个权值,inline函数的复杂度就以这些操作的总和来决定。一般处理一个inline函数有两个阶段:
- 分析函数定义,决定函数的intrinsic inline ability。如果函数因复杂度或构建问题不可成为inline,它会被转为一个static函数,并在被编译模块内产生对应的函数定义。在一个支持模块个别编译的环境中,编译器几乎没有什么权宜之计。理想情况下,链接器会将被产生出来的重复东西清理掉,但调试信息不会。UNIX环境的strip命令可以。
- 真正的inline函数扩展操作是在调用的那一点上,这会带来参数的求值操作(ealuation)以及临时性对象的管理。
在将要扩展的点上,cfront编译器中,inline函数如果只有一个表达式,而又有后续操作,则不会扩展开来。
形式参数(Formal Arguments)
- inline扩展期间每个形式参数都被对应的实际参数取代。如果实际参数是一个常量表达式(constant expression),可以在替换之前完成求值操作(evaluations);如果是个有副作用的表达式,那么需要引入临时性对象;如果既不是常量表达式,也不是带有副作用的表达式,那么就直接代替它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23inline int min(int i, int j)
{
return i < j ? i : j;
}
inline int bar()
{
int minval;
int val1 = 1024;
int val2 = 2048;
minval = min(val1, val2); // 1
minval = min(1024, 2048); // 2
minval = min(foo(), bar() + 1); // 3
return minval;
}
// 扩展
minval = val1 < val2 ? val1 : val2; // 1
minval = 1024; // 2
// 3
int t1;
int t2;
minval = (t1 = foo()), (t2 = bar() + 1), t1 < t2 ? t1 : t1;
局部变量(Local Variables)
- 如果在inline定义中加入局部变量: 一般inline函数中的每个局部变量都必须放在封闭的区段中,拥有独一无二的名称。因为如果inline函数以分离的多个式子(discrete statements)被扩展多次,那么只需要一组局部变量,就可以重复使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23inline int min(int i, int j)
{
int minval = i < j ? i : j;
return minval;
}
{
int local_var;
itn minval;
// ...
minval = min(val1, val2);
}
// 扩展
{
int local_var;
int minval;
// inlnie函数的局部变量处以mangling操作
int __min_lv-minval;
minval =
(__min_lv-minval =
val1 < val2 ? val1 : val2),
__min_lv_minval;
}
inline函数中的局部变量,加上有副作用的参数,可能会导致大量临时性对象的产生。特别是如果以单一表达式(expression)被扩展多次的话:Inline函数对封装提供了必要的支持,可以有效存取class的nonpublic数据。它同时是C程序中大量使用#define的一个安全代替品——特别如果宏中的参数有副作用的话。然而被调用太多次的话,会产生大量的扩展码,使程序大小暴涨。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16minval = min(avl1, val2) + min(foo(), foo()+1);
// 扩展
// 为局部变量产生临时变量
int __min_lv_minval_00;
int __min_lv_minval_01;
// 为放置副作用产生临时变量
int t1;
int t2;
minval = ((__min_lv_minval_00 =
val1 < val2 ? val1 : val2),
__min_lv_minval_00)
+
((__min_lv_minval_01 = (t1 = foo()),
(t1 = foo() + 1),
t1 < t2 ? t1 : t2),
__min_lv_minval__01);