源文章来自前C++标准委员?sh)(x)?nbsp;Danny Kalev ?nbsp;The Biggest Changes in C++11 (and Why You Should Care)Q赖勇浩做了(jin)一?a target="_blank">中文译在这?/a>。所以,我就不翻译了(jin)Q我在这里仅Ҏ(gu)中提到的q些变化“q问Z么要引入q些变化”的一个探讨,只有知道Z(jin)什么,用在什么地方,我们才能真正学到q个知识。而以此你可以更深入地?jin)解q些变化。所以,本文不是译。因为写得有些仓?j),所以难免有问题Q还请大家指正?/p>
Lambda表达式来源于函数式编E,说白׃(jin)是在用的地方定义函数Q有的语a?#8220;闭包”Q如?lambda 函数没有传回?例如 void )Q其回返cd可被完全忽略?定义在与 lambda 函数相同作用域的变量参考也可以被用。这U的变量集合一般被UC closureQ闭包)(j)。我在这里就不再讲这个事?jin)。表辑ּ的简单语法如下,
1 |
[capture](parameters)->return_type {body} |
原文的作者给Z(jin)下面的例子:(x)
1
2
3
4
5
6
7
8
9
10 |
int main() { char s[]= "Hello World!" ; int Uppercase = 0; //modified by the lambda for_each(s, s+ sizeof (s), [&Uppercase] ( char c) { if ( isupper (c)) Uppercase++; }); cout << Uppercase << " uppercase letters in: " << s <<endl; } |
在传l的STL中for_each() q个玩意最后那个参数需要一?#8220;函数对象”Q所谓函数对象,其实是一个classQ这个class重蝲?jin)operator()Q于是这个对象可以像函数的式L(fng)使用。实C个函数对象ƈ不容易,需要用templateQ比如下面这个例子就是函数对象的单例子(实际的实现远比这个复杂)(j)Q?/p>
1
2
3
4
5
6
7
8
9 |
template < class T> class less { public : bool operator()( const T&l, const T&r) const { return l < r; } }; |
所以,C++引入Lambda的最主要原因是1Q可以定义匿名函敎ͼ2Q编译器?x)把其{成函数对?/strong>。相信你?x)和我一P?x)疑问?f)什么以前STL中的ptr_fun()q个函数对象不能用?Qptr_fun()是把一个自然函数{成函数对象的Q。原因是Qptr_fun() 的局限是其接收的自然函数只能??个参数?/p>
那么Q除?jin)方便外Qؓ(f)什么一定要使用Lambda呢?它比传统的函数或是函数对象有什么好处呢Q我个h所理解的是Q这U函Cq以?#8220;闭包”Q就是因为其限制?jin)别人的讉KQ更U有。也可以认ؓ(f)他是一ơ性的Ҏ(gu)。Lambda表达式应该是z的Q极U有的,Z(jin)更易的代码和更方便的~程?/p>
在这一节中Q原文主要介l了(jin)两个关键?auto ?deltypeQ示例如下:(x) auto 最大的好处是让代码简z,其是那些模板类的声明,比如QSTL中的容器的P代子cd?/p>
可以变成Q?/p>
模板q个Ҏ(gu)让C++的代码变得很难读Q不信你可以看看STL的源码,那是一个ؕ啊。用auto必需一个初始化|~译器可以通过q个初始化值推导出cd。因为auto是来化模板类引入的代码难ȝ问题Q如上面的示例,iterationq种cd最适合用auto的,但是Q我们不应该把其滥用?/p>
比如下面的代码的可读性就降低?jin)。因为,我不知道P(yng)rocessDataq回什么?int? bool? q是对象Q或是别的什么?q让你后面的E序不知道怎么做?/p>
但是下面的程序就没有问题Q因为pObject的型别在后面的new中有?jin)?/p>
关于 原文l出的示例如下,我们可以看到Q这个让的确我们的定义变量省?jin)很多事?/p>
q有一个适合的用法是用来typedef函数指针Q也?x)省很多事。比如:(x) Wikipedia 上是q么说的Q关于decltype的规则见上)(j) 如果auto ?decltype 在一起用会(x)是什么样子?能看下面的示例,下面q个CZ也是引入decltype的一个原?#8212;—让C++有能力写一?“ forwarding function 模板”Q?/p>
q个函数模板看v来相当费解,其用C(jin)auto ?decltype 来扩展了(jin)已有的模板技术的不。怎么个不_Q在上例中,我不知道AddingFunc?x)接收什么样cd的对象,q两个对象的 + 操作W返回的cd也不知道Q老的模板函数无法定义AddingFuncq回值和q两个对象相加后的返回值匹配,所以,你可以用上q的q种定义?/p>
C/C++的初始化的方法比较,C++ 11 用大括号l一?jin)这些初始化的方法?/p>
比如QPOD的类型?/p>
关于POD相说两句Q所谓P(yng)OD是Plain Old DataQ当class/struct?em>极简?trivial)、属?em>标准布局(standard-layout)Q以?qing)他的所有非?rn)态(non-staticQ成员都是PODӞ?x)被视?f)POD。如Q?/p>
POD的初始化有点怪,比如上例Qnew A; 和new A(); 是不一L(fng)Q对于其内部的mQ前者没有被初始化,后者被初始化了(jin)Q不?的编译器行ؓ(f)不一PVC++和GCC不一P(j)。而非POD的初始化Q则都会(x)被初始化?/p>
从这点可以看出,C/C++的初始化问题很奇怪,所以,在C++ 2011版中做?jin)统一。原文作者给Z(jin)如下的示例:(x) 容器的初始化Q?/p>
q支持像Java一L(fng)成员初始化:(x) 我们知道C++的编译器在你没有定义某些成员函数的时候会(x)l你的类自动生成q些函数Q比如,构造函敎ͼ拯构造,析构函数Q赋值函数。有些时候,我们不想要这些函敎ͼ比如Q构造函敎ͼ因ؓ(f)我们惛_实现单例模式。传l的做法是将其声明成privatecd?/p>
在新的C++中引入了(jin)两个指示W,delete意ؓ(f)告诉~译器不自动产生q个函数Qdefault告诉~译器生一个默认的。原文给Z(jin)下面两个例子Q?/p>
再如delete q里Q我惌一下,Z么我们需要defaultQ我什么都不写不就是default吗?不全然是Q比如构造函敎ͼ因ؓ(f)只要你定义了(jin)一个构造函敎ͼ~译器就不会(x)l你生成一个默认的?jin)。所以,Z(jin)要让默认的和自定义的共存Q才引入q个参数Q如下例所C:(x) 关于deleteq有两个有用的地Ҏ(gu) 1Q让你的对象只能生成在栈内存?sh)?x) 2Q阻止函数的其Ş参的cd调用Q(若尝试以 double 的Ş参调?nbsp; C/C++的NULL宏是个被有很多潜在BUG的宏。因为有的库把其定义成整?Q有的定义成 (void*)0。在C的时代还好。但是在C++的时代,q就?x)引发很多问题。你可以上网看看。这是ؓ(f)什么需?nbsp; 在以前的C++中,构造函C间不能互相调用,所以,我们在写q些怼的构造函数里Q我们会(x)把相同的代码攑ֈ一个私有的成员函数中?/p>
但是Qؓ(f)?jin)方便ƈ不?#8220;委托构?#8221;q个事出玎ͼ最主要的问题是Q基cȝ构造不能直接成为派生类的构造,q是基cȝ构造函数够?jin),zc还要自己写自己的构造函敎ͼ(x) 上例中,zcL动(h)承基cȝ构造函敎ͼ ~译器可以用基cȝ构造函数完成派生类的构造?而将基类的构造函数带入派生类的动?无法选择性地部分带入Q?所以,要不是l承基类全部的构造函敎ͼ要不是一个都不(h)?不手动带??此外Q若牉|到多重(h)承,从多个基cȝ(h)承而来的构造函C可以有相同的函数{(signature)?而派生类的新加入的构造函C不可以和l承而来的基cL造函数有相同的函数签名,因ؓ(f)q相当于重复声明。(所谓函数签名就是函数的参数cd和顺序不Q?/p>
在老版的C++中,临时性变量(UCؓ(f)叛_?#8221;R-values”Q位于赋值操作符之右Q经常用作交换两个变量。比如下面的CZ中的tmp变量。示例中的那个函数需要传递两个string的引用,但是在交换的q程中生了(jin)对象的构造,内存的分配还有对象的拯构造等{动作,成本比较高?/p>
C++ 11增加一个新的引用(referenceQ类型称作右值引用(R-value referenceQ,标记?tt>typename &&。他们能够以non-const值的方式传入Q允许对象去改动他们。这修正允许特定对象创造出move语义?/p>
举例而言Q上面那个例子中QstringcM保存?sh)(jin)一个动态内存分存的char*指针Q如果一个string对象发生拯构造(如:(x)函数q回Q,stringc里的char*内存只能通过创徏一个新的(f)时对象,q把函数内的对象的内存copy到这个新的对象中Q然后销毁(f)时对象及(qing)其内存?strong>q是原来C++性能上重点被批评的事自动cd推导 auto
auto x=0;
//x has type int because 0 is int
auto c=
'a'
;
//char
auto d=0.5;
//double
auto national_debt=14400000000000LL;
//long long
vector<
int
>::const_iterator ci = vi.begin();
auto ci = vi.begin();
auto obj = ProcessData(someVariables);
auto pObject =
new
SomeType<OtherType>::SomeOtherType();
自动化推?decltype
decltype
是一个操作符Q其可以评估括号内表辑ּ的类型,其规则如下:(x)
const
vector<
int
> vi;
typedef
decltype (vi.begin()) CIT;
CIT another_const_iterator;
decltype(&myfunc) pfunc = 0;
typedef
decltype(&A::func1) type;
auto ?decltype 的差别和关系
#include <vector>
int
main()
{
const
std::vector<
int
> v(1);
auto a = v[0];
// a 的类型是 int
decltype(v[0]) b = 1;
// b 的类型是 const int&, 因ؓ(f)函数的返回类型是
// std::vector<int>::operator[](size_type) const
auto c = 0;
// c 的类型是 int
auto d = c;
// d 的类型是 int
decltype(c) e;
// e 的类型是 int, 因ؓ(f) c 的类型是int
decltype((c)) f = c;
// f 的类型是 int&, 因ؓ(f) (c) 是左?/code>
decltype(0) g;
// g 的类型是 int, 因ؓ(f) 0 是右?/code>
}
template
<
typename
LHS,
typename
RHS>
auto AddingFunc(
const
LHS &lhs,
const
RHS &rhs) -> decltype(lhs+rhs)
{
return
lhs + rhs;}
l一的初始化语法
int
arr[4]={0,1,2,3};
struct
tm
today={0};
struct
A {
int
m; };
// POD
struct
B { ~B();
int
m; };
// non-POD, compiler generated default ctor
struct
C { C() : m() {}; ~C();
int
m; };
// non-POD, default-initialising m
C c {0,0};
//C++11 only. 相当? C c(0,0);
int
* a =
new
int
[3] { 1, 2, 0 }; /C++11 only
class
X {
int
a[4];
public
:
X() : a{1,2,3,4} {}
//C++11, member array initializer
};
// C++11 container initializer
vector<string> vs={
"first"
,
"second"
,
"third"
};
map singers =
{ {
"Lady Gaga"
,
"+1 (212) 555-7890"
},
{
"Beyonce Knowles"
,
"+1 (212) 555-0987"
}};
class
C
{
int
a=7;
//C++11 only
public
:
C();
};
Delete ?Default 函数
struct
A
{
A()=
default
;
//C++11
virtual
~A()=
default
;
//C++11
};
struct
NoCopy
{
NoCopy & operator =(
const
NoCopy & ) =
delete
;
NoCopy (
const
NoCopy & ) =
delete
;
};
NoCopy a;
NoCopy b(a);
//compilation error, copy ctor is deleted
struct
SomeType
{
SomeType() =
default
;
// 使用~译器生成的默认构造函?/code>
SomeType(OtherType value);
};
struct
NonNewable {
void
*operator
new
(std::
size_t
) =
delete
;
};
f()
Q将?x)引发编译期错误Q?~译器不?x)自动?double 形参转型?int 再调?code>f()Q如果传入的参数是doubleQ则?x)出现编译错误?j)
void
f(
int
i);
void
f(
double
) =
delete
;
nullptr
nullptr
的原因?nbsp;nullptr
是强cd的?/p>
void
f(
int
);
//#1
void
f(
char
*);
//#2
//C++03
f(0);
//二义?/code>
//C++11
f(nullptr)
//无二义性,调用f(char*)
所以在新版中请?nullptr
初始化指针?/p>
委托构?/h4>
class
SomeType {
private
:
int
number;
string name;
SomeType(
int
i, string& s ) : number(i), name(s){}
public
:
SomeType( ) : SomeType( 0,
"invalid"
){}
SomeType(
int
i ) : SomeType( i,
"guest"
){}
SomeType( string& s ) : SomeType( 1, s ){ PostInit(); }
};
class
BaseClass
{
public
:
BaseClass(
int
iValue);
};
class
DerivedClass :
public
BaseClass
{
public
:
using
BaseClass::BaseClass;
};
叛_引用和move语义
void
naiveswap(string &a, string &b)
{
string temp = a;
a=b;
b=temp;
}
能过叛_引用,string的构造函数需要改?#8220;move构造函?#8221;Q如下所C。这样一来,使得Ҏ(gu)?span style="font-family: monospace">stirng的右值引用可以单U地从右值复制其内部C-style的指针到新的stringQ然后留下空的右倹{这个操作不需要内存数l的复制Q而且I的暂时对象的析构也不会(x)释放内存。其更有效率?/p>
1
2
3
4
5 |
class string { string (string&&); //move constructor string&& operator=(string&&); //move assignment operator }; |
The C++11 STL中广泛地使用?jin)右值引用和move语议。因此,很多法和容器的性能都被优化?jin)?/p>
C++ STL库在2003q经历了(jin)很大的整Ҏ(gu)?nbsp;Library Technical Report 1 (TR1)?TR1 中出C(jin)很多新的容器c?(unordered_set
, unordered_map
, unordered_multiset
, ?nbsp;unordered_multimap
) 以及(qing)一些新的库支持诸如Q正则表辑ּQ?tuplesQ函数对象包装,{等?C++11 批准?TR1 成ؓ(f)正式的C++标准Q还有一些TR1 后新加的一些库Q从而成Z(jin)新的C++ 11 STL标准库。这个库主要包含下面的功能:(x)
q们׃多说?jin),以前的STL饱受U程安全的批评。现在好 ?jin)。C++ 11 支持U程cM(jin)。这涉?qing)两个部分?x)W一、设计一个可以多个U程在一个进E中共存的内存模型;W二、ؓ(f)U程之间的交互提供支持。第二部分将q序库提供支持。大家可以看?a target="_blank">promises and futuresQ其用于对象的同步?nbsp;async() 函数模板用于发vq发dQ?nbsp;thread_local 为线E内的数据指定存储类型。更多的东西Q可以查?Anthony Williams?nbsp;Simpler Multithreading in C++0x.
C++98 的知能指针是 auto_ptrQ?在C++ 11中被废弃?jin)?/code>C++11 引入?jin)两个指针类Q?nbsp;shared_ptr ?unique_ptr?shared_ptr只是单纯的引用计数指针,
?nbsp;unique_ptr 是用来取?code>auto_ptr
unique_ptr
提供 auto_ptr
大部份特性,唯一的例外是 auto_ptr
的不安全、隐性的左值搬UR不?nbsp;auto_ptr
Q?code>unique_ptr 可以存放?C++0x 提出的那些能察觉搬移动作的容器之中?/p>
Z么要q么qԌ大家可以看看《More Effective C++》中?auto_ptr的讨论?/p>
定义?jin)一些新的算法:(x) all_of()
, any_of()
?nbsp;none_of()?/code>
1
2
3
4
5
6
7
8 |
#include <algorithm> //C++11 code //are all of the elements positive? all_of(first, first+n, ispositive()); //false //is there at least one positive element? any_of(first, first+n, ispositive()); //true // are none of the elements positive? none_of(first, first+n, ispositive()); //false |
使用新的copy_n()法Q你可以很方便地拯数组?/p>
1
2
3
4
5 |
#include <algorithm> int source[5]={0,12,34,50,80}; int target[5]; //copy 5 elements from source to target copy_n(source,5,target); |
使用 iota()
可以用来创徏递增的数列。如下例所C:(x)
1
2
3
4
5 |
include <numeric> int a[5]={0}; char c[3]={0}; iota(a, a+5, 10); //changes a to {10,11,12,13,14} iota(c, c+3, 'a' ); //{'a','b','c'} |
MQ看下来QC++11 q是很学院派Q很多实用的东西q是没有Q比如:(x) XMLQsocketsQreflectionQ当然还有垃圑֛收。看来要{到C++ 20?jin)。呵c(din)不qC++ 11在性能上还是很快。参?Google’s benchmark tests。原文还引用Stroustrup 的观点:(x)C++11 是一门新的语a——一个更好的 C++?/p>
如果把所有的改变都列出来Q你?x)发现真多啊。我估计C++ Primer那本书的厚度要增加至?0%以上。C++的门槛会(x)不会(x)来高?sh)(jin)呢Q我不知道,但我个h觉得q门语言的确是变得越来越令h望而却步了(jin)。(惌v?jin)某人和我说的一句话——学技术真的是太篏?jin),q是搞方法论好些?Q?/p>
Status Of C++ 0x Language Features in Compilers | |||||||||||
C++ 0x FEATURE |
PAPER(S) |
HP aCC |
EDG eccp |
Sun/ Oracle C++ |
Digital Mars C++ |
||||||
alignas |
4.8 |
3.0 | |||||||||
alignof |
4.5 |
Yes |
2.9 | ||||||||
Atomic operations |
4.4 |
13.0 |
11.0 |
3.1 | |||||||
auto |
4.1(v0.9) |
4.4(v1.0) |
11.0(v0.9) |
10.0(v0.9) |
11.1 (V1.0) |
Yes | |||||
C99 preprocessor |
4.3 |
11.1 |
10.1 |
5.9 |
Yes |
Yes | |||||
Concepts [removed] |
|
||||||||||
constexpr |
4.6 |
13.0 |
12.1 |
3.1 | |||||||
decltype |
4.1(v1.0) |
4.3(v1.0) 4.8.1(v1.1) |
11.0(v1.0) |
10.0(v1.0), 11.0(v1.1) |
11.1 (V1.0) |
Yes |
2.9 | ||||
Defaulted And Deleted Functions |
4.1 |
4.4 |
12.0 |
3.0 | |||||||
Delegating Constructors |
4.7 |
|
11.0 nov'12 |
11.1 |
3.0 | ||||||
Explicit conversion operators |
4.5 |
13.0 |
11.0 nov'12 |
12.1 |
Yes |
3.0 | |||||
Extended friend Declarations |
4.1 |
4.7 |
11.0 |
10.0*** |
V1R11,11.1 |
2.9 | |||||
extern template |
3, 5, 6 |
3.3 |
9 |
6.0 |
V1R11,11.1 |
Yes |
Yes | ||||
Forward declarations for enums |
4.6 |
11.0 |
12.1 |
3.1 | |||||||
Inheriting Constructors |
4.8 |
|
|
||||||||
Initializer Lists |
4.4 |
13.0 |
11.0 nov'12 |
3.1 | |||||||
Lambda expressions and closures |
4.1(v0.9) |
4.5(v1.1) |
11.0(v0.9) |
10.0(v1.0), 11.0(v1.1) |
3.1 | ||||||
Local and Unnamed Types as Template Arguments |
4.5 |
12.0 |
10.0 |
2.9 | |||||||
long long |
Yes |
Yes |
Yes |
Yes |
Yes |
Yes |
Yes |
Yes |
Yes |
Yes | |
Namespace Association |
4.4 |
|
11.1 |
2.9 | |||||||
New character types |
4.4 |
2.9 | |||||||||
New function declaration syntax for deduced return types |
4.1 |
4.4 |
12.1 |
10.0 |
12.1 |
2.9 | |||||
nullptr |
4.6 |
12.1* |
10.0 |
2.9 | |||||||
Unicode String Literals |
4.4 |
11.0* |
5.7 |
Yes |
3.0 | ||||||
Raw String Literals |
4.5 |
|
11.0 nov'12 |
Yes | |||||||
User-defined Literals |
4.7 |
3.1 | |||||||||
Right Angle Brackets |
4.1 |
4.3 |
11.0 |
8.0 |
12.1 |
Yes | |||||
R-Value References, std::move |
4.1(v1.0) |
4.3(v1.0) |
11.1(v1.0) |
10.0(v2.0), 11.0(v2.1) |
12.1(v2.1) |
Yes |
Yes | ||||
static_assert |
4.1 |
4.3 |
11.0 |
10.0 |
11.1 |
Yes |
2.9 | ||||
Strongly-typed enums |
4.4 |
12.0 |
11.0 |
12.1 |
Yes |
2.9 | |||||
Template aliases |
4.7 |
12.1 |
3.0 | ||||||||
Thread-Local Storage |
4.8 (4.4****) |
11.1*** |
10.0*** |
5.9*** |
2.9**** | ||||||
Unrestricted Unions |
4.6 |
|
3.0 | ||||||||
Built-in Type Traits |
6.16 |
4.0 |
4.3 |
10.0 |
8.0 |
Yes |
3.0 | ||||
Variadic Templates |
4.1(v0.9) |
4.3(v0.9) 4.4(v1.0) |
12.1(v0.9) |
11.0 nov'12 |
11.1 (v0.9) |
2.9(1.0) | |||||
Range-based for-loop |
4.6 |
13.0 |
11.0 |
3.0 | |||||||
override and final |
4.7 |
12.0(v0.8)*** |
8.0(v0.8)*** 11.0(v1.0) |
2.9 | |||||||
Attributes |
4.8 |
12.1 |
|||||||||
ref-qualifiers |
4.8.1 |
2.9 | |||||||||
Non-static data member initializers |
4.7 |
3.0 | |||||||||
Dynamic initialization and destruction with concurrency (Magic statics) |
4.3 |
? |
2.9 |
* — 1) unicode string literals is a feature of the EDG frontend, but it is undocumented at Intel C++ compiler (/Qoption,cpp,"--uliterals" option enables it)
gprof是GNU profiler工具。可以显C程序运行的“flat profile”Q包括每个函数的调用ơ数Q每个函数消耗的处理器时间。也可以昄“调用?#8221;Q包括函数的调用关系Q每个函数调用花费了(jin)多少旉。还可以昄“注释的源代码”Q是E序源代码的一个复本,标记有程序中每行代码的执行次数?/p>
在编译或链接源程序的时候在~译器的命o(h)行参C加入“-pg”选项Q编译时~译器会(x)自动在目标代码中插入用于性能试的代码片断,q些代码在程序在q行旉集ƈ记录函数的调用关pd调用ơ数Q以?qing)采集ƈ记录函数自n执行旉和子函数的调用时_(d)E序q行l束后,?x)在E序退出的路径下生成一个gmon.out文g。这个文件就是记录ƈ保存?sh)来的监控数据。可以通过命o(h)行方式的gprof或图形化的Kprof来解读这些数据ƈ对程序的性能q行分析。另外,如果x(chng)看库函数的profilingQ需要在~译是再加入“-lc_p”~译参数代替“-lc”~译参数Q这L(fng)序会(x)链接libc_p.a库,才可以生库函数的profiling信息。如果想执行一行一行的profilingQ还需要加?#8220;-g”~译参数?br />例如如下命o(h)行:(x)gcc -Wall -g -pg -lc_p example.c -o example
1Q?使用 -pg ~译和链接你的应用程序?/p>
2Q?执行你的应用E序使之生成供gprof 分析的数据?
3Q?使用gprof E序分析你的应用E序生成的数据?
$gprof -b a.out gmon.out
Flat profile:
Each sample counts as 0.01 seconds.
no time accumulated
% cumulative self self total
time seconds seconds calls Ts/call Ts/call name
0.00 0.00 0.00 1 0.00 0.00 function
Call graph
granularity: each sample hit covers 2 byte(s) no time propagated
index % time self children called name
0.00 0.00 1/1 main [8]
[1] 0.0 0.00 0.00 1 function [1]
-----------------------------------------------
Index by function name
[1] function
% the percentage of the total running time of the
time program used by this function.
函数使用旉占所有时间的癑ֈ比?br />cumulative a running sum of the number of seconds accounted
seconds for by this function and those listed above it.
函数和上列函数篏计执行的旉?br />self the number of seconds accounted for by this
seconds function alone. This is the major sort for this
listing.
函数本n所执行的时间?br />calls the number of times this function was invoked, if
this function is profiled, else blank.
函数被调用的ơ数
self the average number of milliseconds spent in this
ms/call function per call, if this function is profiled,
else blank.
每一ơ调用花费在函数的时间microseconds?br />total the average number of milliseconds spent in this
ms/call function and its descendents per call, if this
function is profiled, else blank.
每一ơ调用,p在函数及(qing)其衍生函数的q_旉microseconds?br />name the name of the function. This is the minor sort
for this listing. The index shows the location of
the function in the gprof listing. If the index is
in parenthesis it shows where it would appear in
the gprof listing if it were to be printed.
函数?/p>
gprof [可执行文件] [gmon.out文g] [其它参数]
Ҏ(gu)号中的内容可以省略。如果省略了(jin)“可执行文?#8221;Qgprof?x)在当前目录下搜索a.out文g作ؓ(f)可执行文Ӟ而如果省略了(jin)gmon.out文gQgprof也会(x)在当前目录下Lgmon.out。其它参数可以控制gprof输出内容的格式等信息。最常用的参数如下:(x)
l -b 不再输出l计图表中每个字D늚详细描述?
l -p 只输出函数的调用图(Call graph的那部分信息Q?
l -q 只输出函数的旉消耗列表?
l -e Name 不再输出函数Name ?qing)其子函数的调用图(除非它们有未被限制的其它父函敎ͼ?j)。可以给定多?-e 标志。一?-e 标志只能指定一个函数?
l -E Name 不再输出函数Name ?qing)其子函数的调用图,此标志类g -e 标志Q但它在L间和癑ֈ比时间的计算中排除了(jin)由函数Name ?qing)其子函数所用的旉?
l -f Name 输出函数Name ?qing)其子函数的调用图。可以指定多?-f 标志。一?-f 标志只能指定一个函数?
l -F Name 输出函数Name ?qing)其子函数的调用图,它类g -f 标志Q但它在L间和癑ֈ比时间计中仅用所打印的例E的旉。可以指定多?-F 标志。一?-F 标志只能指定一个函数?F 标志覆盖 -E 标志?
l -z 昄使用ơ数为零的例E(按照调用计数和篏U时间计)(j)?
不过,gprof不能昄对象之间的(h)承关p?q也是它的弱?
据的高(sh)保存在内存的高地址中,q种存储模式地址的高?sh)和数据位权有效地结合v来,高地址部分权值高Q低地址部分权gQ和我们的逻辑Ҏ(gu)一致?/span>
8bitC8bitchar16bitshort32bitlong 8163216bitshortx0x0010x0x11220x110x22大端模式Q就?/span>0x110x00100x220x0011X86KEIL C51ARMDSPARM二、D例说?/span>
AASCII650x4100 00 00 41 -----41 00 00 00 -----三、用代码判断大端模式
union/** * 得到当前pȝ的大端属?/span>, 32 */ static union { char c[4]; unsigned long l; } endian_test = { { 'l', '?', '?', 'b' } }; #define ENDIANNESS ((char)endian_test.l) /** * : */ static union { short n; char c[sizeof(short)]; }un; int getEndian() { un.n = 0x0102; if ((un.c[0] == 1 && un.c[1] == 2)) { printf("big endian/n"); } else if ((un.c[0] == 2 && un.c[1] == 1)) { printf("little endian/n"); } else printf("error!/n"); return 0; } /** * : */ int getEndian() { int c = 1; // big-endian: 00 00 00 01 little-endian: 01 00 00 00 // int c = 0x02000001; // big-endian: 02 00 00 01 little-endian: 01 00 00 02 if ((*(char *)&c) == 1) // c { printf("little endian/n"); } else printf("big endian"); return 0; }
]]>
前言
07q?2月,我写?jin)一《C++虚函数表解析》的文章Q引起了(jin)大家的兴。有很多朋友Ҏ(gu)的文章留?jin)言Q有鼓励我的Q有批评我的Q还有很多问问题的。我在这里一q对大家的留a表示感谢。这也是我ؓ(f)什么再写一箋(hu)a的原因。因为,在上一文章中Q我用了(jin)的示例都是非常简单的Q主要是Z(jin)说明一些机理上的问题,也是Z(jin)图一些表达上方便和简单。不惻Iq篇文章成ؓ(f)?jin)打开C++对象模型内存布局的一个引子,引发?jin)大家对C++对象的更深层ơ的讨论。当?dng)我之前的文章q有很多斚w没有涉及(qing)Q从我个人感觉下来,在谈函数表里Q至有以下q些内容没有涉及(qing)Q?/p>
1Q有成员变量的情c(din)?/p>
2Q有重复l承的情c(din)?/p>
3Q有虚拟l承的情c(din)?/p>
4Q有ȝ型虚拟(h)承的情况?/p>
q些都是我本文章需要向大家说明的东ѝ所以,q篇文章会(x)是《C++虚函数表解析》的一个箋(hu),也是一高U进阶的文章。我希望大家在读q篇文章之前对C++有一定的基础和了(jin)解,q能先读我的上一文章。因文章的深度可能?x)比较深Q而且?x)比较杂乱,我希望你在读本篇文章时不会(x)有大脑思维紊ؕD大脑L的情c(din)?-)
对象的媄(jing)响因?/p>
而言之,我们一个类可能?x)有如下的?jing)响因素:(x)
1Q成员变?/p>
2Q虚函数Q生虚函数表)(j)
3Q单一l承Q只l承于一个类Q?/p>
4Q多重(h)承(l承多个c)(j)
5Q重复(h)承(l承的多个父cM其父cL相同的超c)(j)
6Q虚拟(h)承(使用virtual方式l承Qؓ(f)?jin)保证?h)承后父类的内存布局只会(x)存在一份)(j)
上述的东襉K常是C++q门语言在语义方面对对象内部的媄(jing)响因素,当然Q还?sh)(x)有~译器的影响Q比如优化)(j)Q还有字节对齐的影响。在q里我们都不讨论Q我们只讨论C++语言上的影响?/p>
本篇文章着重讨Zq几个情况下的C++对象的内存布局情况?/p>
1Q单一的一般(h)承(带成员变量、虚函数、虚函数覆盖Q?/p>
2Q单一的虚拟(h)承(带成员变量、虚函数、虚函数覆盖Q?/p>
3Q多重(h)承(带成员变量、虚函数、虚函数覆盖Q?/p>
4Q重复多重(h)承(带成员变量、虚函数、虚函数覆盖Q?/p>
5Q钻矛_的虚拟多重(h)承(带成员变量、虚函数、虚函数覆盖Q?/p>
我们的目标就是,让事情越来越复杂?/p>
知识复习(fn)
我们单地复习(fn)一下,我们可以通过对象的地址来取得虚函数表的地址Q如Q?/p>
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虚函数表地址Q? << (int*)(&b) << endl;
cout << "虚函数表 ?W一个函数地址Q? << (int*)*(int*)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
我们同样可以用这U方式来取得整个对象实例的内存布局。因些东西在内存?sh)都是连l分布的Q我们只需要用适当的地址偏移量,我们可以获得整个内存对象的布局?/p>
本篇文章中的例程或内存布局主要使用如下~译器和pȝQ?/p>
1QWindows XP ?VC++ 2003
2QCygwin ?G++ 3.4.4
单一的一般(h)?/strong>
下面Q我们假设有如下所C的一个(h)承关p:(x)
h意,在这个(h)承关pMQ父c,子类Q孙子类都有自己的一个成员变量。而了(jin)c覆盖了(jin)父类的f()Ҏ(gu)Q孙子类覆盖?jin)子cȝg_child()?qing)其类的f()?/p>
我们的源E序如下所C:(x)
class Parent {
public:
int iparent;
Parent ():iparent (10) {}
virtual void f() { cout << " Parent::f()" << endl; }
virtual void g() { cout << " Parent::g()" << endl; }
virtual void h() { cout << " Parent::h()" << endl; }
};
class Child : public Parent {
public:
int ichild;
Child():ichild(100) {}
virtual void f() { cout << "Child::f()" << endl; }
virtual void g_child() { cout << "Child::g_child()" << endl; }
virtual void h_child() { cout << "Child::h_child()" << endl; }
};
class GrandChild : public Child{
public:
int igrandchild;
GrandChild():igrandchild(1000) {}
virtual void f() { cout << "GrandChild::f()" << endl; }
virtual void g_child() { cout << "GrandChild::g_child()" << endl; }
virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }
};
我们使用以下E序作ؓ(f)试E序Q(下面E序中,我用了(jin)一个int** pVtab 来作为遍历对象内存布局的指针,q样Q我可以方便地像用数l一h遍历所有的成员包括其虚函数表了(jin)Q在后面的程序中Q我也是用这L(fng)Ҏ(gu)的,请不必感到奇怪,Q?/p>
typedef void(*Fun)(void);
GrandChild gc;
int** pVtab = (int**)&gc;
cout << "[0] GrandChild::_vptr->" << endl;
for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){
pFun = (Fun)pVtab[0][i];
cout << " ["<<i<<"] ";
pFun();
}
cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;
cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;
cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;
其运行结果如下所C:(x)Q在VC++ 2003和G++ 3.4.4下)(j)
[0] GrandChild::_vptr-> [0] GrandChild::f() [1] Parent::g() [2] Parent::h() [3] GrandChild::g_child() [4] Child::h1() [5] GrandChild::h_grandchild() [1] Parent.iparent = 10 [2] Child.ichild = 100 [3] GrandChild.igrandchild = 1000 |
使用囄表示如下Q?/p>
可见以下几个斚wQ?/p>
1Q虚函数表在最前面的位|?/p>
2Q成员变量根据其l承和声明顺序依ơ放在后面?/p>
3Q在单一的(h)承中Q被overwrite的虚函数在虚函数表中得到?jin)更新?/p>
多重l承
下面Q再让我们来看看多重l承中的情况Q假设有下面q样一个类的(h)承关pR注意:(x)子类只overwrite?jin)父cȝf()函数Q而还有一个是自己的函敎ͼ我们q样做的目的是ؓ(f)?jin)用g1()作ؓ(f)一个标记来标明子类的虚函数表)(j)。而且每个cM都有一个自q成员变量Q?/p>
我们的类l承的源代码如下所C:(x)父类的成员初始ؓ(f)10Q?0Q?0Q子cȝ?00
class Base1 {
public:
int ibase1;
Base1():ibase1(10) {}
virtual void f() { cout << "Base1::f()" << endl; }
virtual void g() { cout << "Base1::g()" << endl; }
virtual void h() { cout << "Base1::h()" << endl; }
};
class Base2 {
public:
int ibase2;
Base2():ibase2(20) {}
virtual void f() { cout << "Base2::f()" << endl; }
virtual void g() { cout << "Base2::g()" << endl; }
virtual void h() { cout << "Base2::h()" << endl; }
};
class Base3 {
public:
int ibase3;
Base3():ibase3(30) {}
virtual void f() { cout << "Base3::f()" << endl; }
virtual void g() { cout << "Base3::g()" << endl; }
virtual void h() { cout << "Base3::h()" << endl; }
};
class Derive : public Base1, public Base2, public Base3 {
public:
int iderive;
Derive():iderive(100) {}
virtual void f() { cout << "Derive::f()" << endl; }
virtual void g1() { cout << "Derive::g1()" << endl; }
};
我们通过下面的程序来查看子类实例的内存布局Q下面程序中Q注意我使用?jin)一个s变量Q其中用C(jin)sizof(Base)来找下一个类的偏U量。(因ؓ(f)我声明的是int成员Q所以是4个字节,所以没有对齐问题。关于内存的寚w问题Q大家可以自行试验,我在q里׃多说?jin)?j)
typedef void(*Fun)(void);
Derive d;
int** pVtab = (int**)&d;
cout << "[0] Base1::_vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << " [0] ";
pFun();
pFun = (Fun)pVtab[0][1];
cout << " [1] ";pFun();
pFun = (Fun)pVtab[0][2];
cout << " [2] ";pFun();
pFun = (Fun)pVtab[0][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[0][4];
cout << " [4] "; cout<<pFun<<endl;
cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;
int s = sizeof(Base1)/4;
cout << "[" << s << "] Base2::_vptr->"<<endl;
pFun = (Fun)pVtab[s][0];
cout << " [0] "; pFun();
Fun = (Fun)pVtab[s][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[s][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[s][3];
out << " [3] ";
cout<<pFun<<endl;
cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl;
s = s + sizeof(Base2)/4;
cout << "[" << s << "] Base3::_vptr->"<<endl;
pFun = (Fun)pVtab[s][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[s][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[s][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[s][3];
cout << " [3] ";
cout<<pFun<<endl;
s++;
cout << "["<< s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl;
s++;
cout << "["<< s <<"] Derive.iderive = " << (int)pVtab[s] << endl;
其运行结果如下所C:(x)Q在VC++ 2003和G++ 3.4.4下)(j)
[0] Base1::_vptr-> [0] Derive::f() [1] Base1::g() [2] Base1::h() [3] Driver::g1() [4] 00000000 ç 注意Q在GCC下,q里?/span>1 [1] Base1.ibase1 = 10 [2] Base2::_vptr-> [0] Derive::f() [1] Base2::g() [2] Base2::h() [3] 00000000 ç 注意Q在GCC下,q里?/span>1 [3] Base2.ibase2 = 20 [4] Base3::_vptr-> [0] Derive::f() [1] Base3::g() [2] Base3::h() [3] 00000000 [5] Base3.ibase3 = 30 [6] Derive.iderive = 100 |
使用囄表示是下面这个样子:(x)
我们可以看到Q?/p>
1Q?每个父类都有自己的虚表?/p>
2Q?子类的成员函数被攑ֈ?jin)第一个父cȝ表中?/p>
3Q?内存布局中,其父cd局依次按声明顺序排列?/p>
4Q?每个父类的虚表中的f()函数都被overwrite成了(jin)子类的f()。这样做是Z(jin)解决不同的父cȝ型的指针指向同一个子cd例,而能够调用到实际的函数?/p>
重复l承
下面我们再来看看Q发生重复(h)承的情况。所谓重复(h)承,也就是某个基c被间接地重复(h)承了(jin)多次?/p>
下图是一个(h)承图Q我们重载了(jin)父类的f()函数?/p>
其类l承的源代码如下所C。其中,每个c都有两个变量,一个是整ŞQ?字节Q,一个是字符Q?字节Q,而且q有自己的虚函数Q自己overwrite父类的虚函数。如子类D中,f()覆盖?jin)超cȝ函数Q?f1() 和f2() 覆盖?jin)其父类的虚函数QDf()q虚函数?/p>
class B
{
public:
int ib;
char cb;
public:
B():ib(0),cb('B') {}
virtual void f() { cout << "B::f()" << endl;}
virtual void Bf() { cout << "B::Bf()" << endl;}
};
class B1 : public B
{
public:
int ib1;
char cb1;
public:
B1():ib1(11),cb1('1') {}
virtual void f() { cout << "B1::f()" << endl;}
virtual void f1() { cout << "B1::f1()" << endl;}
virtual void Bf1() { cout << "B1::Bf1()" << endl;}
};
class B2: public B
{
public:
int ib2;
char cb2;
public:
B2():ib2(12),cb2('2') {}
virtual void f() { cout << "B2::f()" << endl;}
virtual void f2() { cout << "B2::f2()" << endl;}
virtual void Bf2() { cout << "B2::Bf2()" << endl;}
};
class D : public B1, public B2
{
public:
int id;
char cd;
public:
D():id(100),cd('D') {}
virtual void f() { cout << "D::f()" << endl;}
virtual void f1() { cout << "D::f1()" << endl;}
virtual void f2() { cout << "D::f2()" << endl;}
virtual void Df() { cout << "D::Df()" << endl;}
};
我们用来存取子类内存布局的代码如下所C:(x)Q在VC++ 2003和G++ 3.4.4下)(j)
typedef void(*Fun)(void);
int** pVtab = NULL;
Fun pFun = NULL;
D d;
pVtab = (int**)&d;
cout << "[0] D::B1::_vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[0][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[0][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[0][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[0][4];
cout << " [4] "; pFun();
pFun = (Fun)pVtab[0][5];
cout << " [5] 0x" << pFun << endl;
cout << "[1] B::ib = " << (int)pVtab[1] << endl;
cout << "[2] B::cb = " << (char)pVtab[2] << endl;
cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;
cout << "[4] B1::cb1 = " << (char)pVtab[4] << endl;
cout << "[5] D::B2::_vptr->" << endl;
pFun = (Fun)pVtab[5][0];
cout << " [0] "; pFun();
pFun = (Fun)pVtab[5][1];
cout << " [1] "; pFun();
pFun = (Fun)pVtab[5][2];
cout << " [2] "; pFun();
pFun = (Fun)pVtab[5][3];
cout << " [3] "; pFun();
pFun = (Fun)pVtab[5][4];
cout << " [4] 0x" << pFun << endl;
cout << "[6] B::ib = " << (int)pVtab[6] << endl;
cout << "[7] B::cb = " << (char)pVtab[7] << endl;
cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;
cout << "[9] B2::cb2 = " << (char)pVtab[9] << endl;
cout << "[10] D::id = " << (int)pVtab[10] << endl;
cout << "[11] D::cd = " << (char)pVtab[11] << endl;
E序q行l果如下Q?/p>
GCC 3.4.4 VC++ 2003 [0] D::B1::_vptr-> [0] D::f() [1] B::Bf() [2] D::f1() [3] B1::Bf1() [4] D::f2() [5] 0x1 [1] B::ib = 0 [2] B::cb = B [3] B1::ib1 = 11 [4] B1::cb1 = 1 [5] D::B2::_vptr-> [0] D::f() [1] B::Bf() [2] D::f2() [3] B2::Bf2() [4] 0x0 [6] B::ib = 0 [7] B::cb = B [8] B2::ib2 = 12 [9] B2::cb2 = 2 [10] D::id = 100 [11] D::cd = D [0] D::B1::_vptr-> [0] D::f() [1] B::Bf() [2] D::f1() [3] B1::Bf1() [4] D::Df() [5] 0x00000000 [1] B::ib = 0 [2] B::cb = B [3] B1::ib1 = 11 [4] B1::cb1 = 1 [5] D::B2::_vptr-> [0] D::f() [1] B::Bf() [2] D::f2() [3] B2::Bf2() [4] 0x00000000 [6] B::ib = 0 [7] B::cb = B [8] B2::ib2 = 12 [9] B2::cb2 = 2 [10] D::id = 100 [11] D::cd = D
下面是对于子cd例中的虚函数表的图:(x)
我们可以看见Q最端的父cB其成员变量存在于B1和B2中,q被Dl(h)承下M(jin)。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两䆾Q一份是B1l承而来的,另一份是B2l承而来的。所以,如果我们使用以下语句Q则?x)生二义性编译错误:(x)
D d;
d.ib = 0; //二义性错?/p>
d.B1::ib = 1; //正确
d.B2::ib = 2; //正确
注意Q上面例E中的最后两条语句存取的是两个变量。虽然我们消除了(jin)二义性的~译错误Q但BcdD中还是有两个实例Q这U(h)扉K成?jin)数据的重复Q我们叫q种l承为重复(h)ѝ重复的基类数据成员可能q不是我们想要的。所以,C++引入?jin)虚基类的概c(din)?/p>
ȝ型多重虚拟(h)?/strong>
虚拟l承的出现就是ؓ(f)?jin)解决重复?h)承中多个间接父类的问题的。钻矛_的结构是其最l典的结构。也是我们在q里要讨论的l构Q?/p>
上述?#8220;重复l承”只需要把B1和B2l承B的语法中加上virtual 关键Q就成了(jin)虚拟l承Q其l承囑֦下所C:(x)
上图和前面的“重复l承”中的cȝ内部数据和接口都是完全一L(fng)Q只是我们采用了(jin)虚拟l承Q其省略后的源码如下所C:(x)
class B {……};
class B1 : virtual public B{……};
class B2: virtual public B{……};
class D : public B1, public B2{ …… };
在查看D之前Q我们先看一看单一虚拟l承的情c(din)下面是一D在VC++2003下的试E序Q(因ؓ(f)VC++和GCC的内存而局上有一些细节上的不同,所以这里只l出VC++的程序,GCC下的E序大家可以Ҏ(gu)我给出的E序自己仿照着写一个去试一试)(j)Q?/p>
int** pVtab = NULL;
Fun pFun = NULL;
B1 bb1;
pVtab = (int**)&bb1;
cout << "[0] B1::_vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << " [0] ";
pFun(); //B1::f1();
cout << " [1] ";
pFun = (Fun)pVtab[0][1];
pFun(); //B1::bf1();
cout << " [2] ";
cout << pVtab[0][2] << endl;
cout << "[1] = 0x";
cout << (int*)*((int*)(&bb1)+1) <<endl; //B1::ib1
cout << "[2] B1::ib1 = ";
cout << (int)*((int*)(&bb1)+2) <<endl; //B1::ib1
cout << "[3] B1::cb1 = ";
cout << (char)*((int*)(&bb1)+3) << endl; //B1::cb1
cout << "[4] = 0x";
cout << (int*)*((int*)(&bb1)+4) << endl; //NULL
cout << "[5] B::_vptr->" << endl;
pFun = (Fun)pVtab[5][0];
cout << " [0] ";
pFun(); //B1::f();
pFun = (Fun)pVtab[5][1];
cout << " [1] ";
pFun(); //B::Bf();
cout << " [2] ";
cout << "0x" << (Fun)pVtab[5][2] << endl;
cout << "[6] B::ib = ";
cout << (int)*((int*)(&bb1)+6) <<endl; //B::ib
cout << "[7] B::cb = ";
其运行结果如下(我结Z(jin)GCC的和VC++2003的对比)(j)Q?/p>
GCC 3.4.4 |
VC++ 2003 |
[0] B1::_vptr -> [0] : B1::f() [1] : B1::f1() [2] : B1::Bf1() [3] : 0 [1] B1::ib1 : 11 [2] B1::cb1 : 1 [3] B::_vptr -> [0] : B1::f() [1] : B::Bf() [2] : 0 [4] B::ib : 0 [5] B::cb : B [6] NULL : 0 |
[0] B1::_vptr-> [0] B1::f1() [1] B1::Bf1() [2] 0 [1] = 0x00454310 ç该地址取值后?/span>-4 [2] B1::ib1 = 11 [3] B1::cb1 = 1 [4] = 0x00000000 [5] B::_vptr-> [0] B1::f() [1] B::Bf() [2] 0x00000000 [6] B::ib = 0 [7] B::cb = B |
q里Q大家可以自己对比一下。关于细节上Q我?x)在后面一q再说?/p>
下面的测试程序是看子cD的内存布局Q同hVC++ 2003的(因ؓ(f)VC++和GCC的内存布局上有一些细节上的不同,而VC++的相对要清楚很多Q所以这里只l出VC++的程序,GCC下的E序大家可以Ҏ(gu)我给出的E序自己仿照着写一个去试一试)(j)Q?/p>
D d;
pVtab = (int**)&d;
cout << "[0] D::B1::_vptr->" << endl;
pFun = (Fun)pVtab[0][0];
cout << " [0] "; pFun(); //D::f1();
pFun = (Fun)pVtab[0][1];
cout << " [1] "; pFun(); //B1::Bf1();
pFun = (Fun)pVtab[0][2];
cout << " [2] "; pFun(); //D::Df();
pFun = (Fun)pVtab[0][3];
cout << " [3] ";
cout << pFun << endl;
//cout << pVtab[4][2] << endl;
cout << "[1] = 0x";
cout << (int*)((&dd)+1) <<endl; //????
cout << "[2] B1::ib1 = ";
cout << *((int*)(&dd)+2) <<endl; //B1::ib1
cout << "[3] B1::cb1 = ";
cout << (char)*((int*)(&dd)+3) << endl; //B1::cb1
//---------------------
cout << "[4] D::B2::_vptr->" << endl;
pFun = (Fun)pVtab[4][0];
cout << " [0] "; pFun(); //D::f2();
pFun = (Fun)pVtab[4][1];
cout << " [1] "; pFun(); //B2::Bf2();
pFun = (Fun)pVtab[4][2];
cout << " [2] ";
cout << pFun << endl;
cout << "[5] = 0x";
cout << *((int*)(&dd)+5) << endl; // ???
cout << "[6] B2::ib2 = ";
cout << (int)*((int*)(&dd)+6) <<endl; //B2::ib2
cout << "[7] B2::cb2 = ";
cout << (char)*((int*)(&dd)+7) << endl; //B2::cb2
cout << "[8] D::id = ";
cout << *((int*)(&dd)+8) << endl; //D::id
cout << "[9] D::cd = ";
cout << (char)*((int*)(&dd)+9) << endl;//D::cd
cout << "[10] = 0x";
cout << (int*)*((int*)(&dd)+10) << endl;
//---------------------
cout << "[11] D::B::_vptr->" << endl;
pFun = (Fun)pVtab[11][0];
cout << " [0] "; pFun(); //D::f();
pFun = (Fun)pVtab[11][1];
cout << " [1] "; pFun(); //B::Bf();
pFun = (Fun)pVtab[11][2];
cout << " [2] ";
cout << pFun << endl;
cout << "[12] B::ib = ";
cout << *((int*)(&dd)+12) << endl; //B::ib
cout << "[13] B::cb = ";
cout << (char)*((int*)(&dd)+13) <<endl;//B::cb
下面l出q行后的l果Q分VC++和GCC两部份)(j)
GCC 3.4.4 |
VC++ 2003 |
[0] B1::_vptr -> [0] : D::f() [1] : D::f1() [2] : B1::Bf1() [3] : D::f2() [4] : D::Df() [5] : 1 [1] B1::ib1 : 11 [2] B1::cb1 : 1 [3] B2::_vptr -> [0] : D::f() [1] : D::f2() [2] : B2::Bf2() [3] : 0 [4] B2::ib2 : 12 [5] B2::cb2 : 2 [6] D::id : 100 [7] D::cd : D [8] B::_vptr -> [0] : D::f() [1] : B::Bf() [2] : 0 [9] B::ib : 0 [10] B::cb : B [11] NULL : 0 |
[0] D::B1::_vptr-> [0] D::f1() [1] B1::Bf1() [2] D::Df() [3] 00000000 [1] = 0x0013FDC4 ç 该地址取值后?/span>-4 [2] B1::ib1 = 11 [3] B1::cb1 = 1 [4] D::B2::_vptr-> [0] D::f2() [1] B2::Bf2() [2] 00000000 [5] = 0x4539260 ç 该地址取值后?/span>-4 [6] B2::ib2 = 12 [7] B2::cb2 = 2 [8] D::id = 100 [9] D::cd = D [10] = 0x00000000 [11] D::B::_vptr-> [0] D::f() [1] B::Bf() [2] 00000000 [12] B::ib = 0 [13] B::cb = B |
关于虚拟l承的运行结果我׃d?jin)(前面的作囑ַl让我生了(jin)很严重的厌倦感Q所以就偷个懒了(jin)Q大家见谅了(jin)Q?/p>
在上面的输出l果中,我用不同的颜色做?jin)一些标明。我们可以看到如下的几点Q?/p>
1Q无论是GCCq是VC++Q除?jin)一些细节上的不同,其大体上的对象布局是一L(fng)。也是_(d)先是B1Q黄Ԍ(j)Q然后是B2Q绿Ԍ(j)Q接着是DQ灰Ԍ(j)Q而Bq个类Q青蓝色Q的实例都放在最后的位置?/p>
2Q关于虚函数表,其是第一个虚表,GCC和VC++有很重大的不一栗但仔细看下来,q是VC++的虚表比较清晰和有逻辑性?/p>
3QVC++和GCC都把Bq个类攑ֈ?jin)最后,而VC++有一个NULL分隔W把B和B1和B2的布局分开。GCC则没有?/p>
4QVC++中的内存布局有两个地址我有些不是很明白Q在其中我用U色标出?jin)。取其内Ҏ(gu)-4。接道理来说Q这个指针应该是指向B(ti)cd例的内存地址Q这个做法就是ؓ(f)?jin)保证重复的父类只有一个实例的技术)(j)。但取值后却不是。这Ҏ(gu)目前qƈ不太清楚Q还向大家请教?/p>
5QGCC的内存布局中在B1和B2中则没有指向B(ti)的指针。这点可以理解,~译器可以通过计算B1和B2的size而得出B的偏U量?/p>
l束?/strong>
C++q门语言是一门比较复杂的语言Q对于程序员来说Q我们似乎永q摸不清楚这门语a背着我们在干?jin)什么。需要熟(zhn)这门语aQ我们就必需要了(jin)解C++里面的那些东西,需要我们去?jin)解他后面的内存对象。这h们才能真正的?jin)解C++Q从而能够更好的使用C++q门最隄~程语言?/p>
在文章束之前q是介绍一下自己吧。我从事软g研发有十个年头了(jin)Q目前是软g开发技术主,技术方面,LUnix/C/C++Q比较喜Ƣ网l上的技术,比如分布式计,|格计算QP2PQAjax{一切和互联|相关的东西。管理方面比较擅长于团队Q技术趋势分析,目理。欢q大家和我交,我的MSN和Email是:(x)haoel@hotmail.com