源文章来自前C++标准委员会的 Danny Kalev ?nbsp;The Biggest Changes in C++11 (and Why You Should Care)Q赖勇浩做了一?a target="_blank">中文译在这?/a>。所以,我就不翻译了Q我在这里仅Ҏ中提到的q些变化“q问Z么要引入q些变化”的一个探讨,只有知道Z什么,用在什么地方,我们才能真正学到q个知识。而以此你可以更深入地了解q些变化。所以,本文不是译。因为写得有些仓促,所以难免有问题Q还请大家指正?/p>
Lambda表达式来源于函数式编E,说白׃是在用的地方定义函数Q有的语a?#8220;闭包”Q如?lambda 函数没有传回?例如 void )Q其回返cd可被完全忽略?定义在与 lambda 函数相同作用域的变量参考也可以被用。这U的变量集合一般被UC closureQ闭包)。我在这里就不再讲这个事了。表辑ּ的简单语法如下,
1 |
[capture](parameters)->return_type {body} |
原文的作者给Z下面的例子:
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重蝲了operator()Q于是这个对象可以像函数的式L使用。实C个函数对象ƈ不容易,需要用templateQ比如下面这个例子就是函数对象的单例子(实际的实现远比这个复杂)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编译器会把其{成函数对?/strong>。相信你会和我一P会疑问ؓ什么以前STL中的ptr_fun()q个函数对象不能用?Qptr_fun()是把一个自然函数{成函数对象的Q。原因是Qptr_fun() 的局限是其接收的自然函数只能??个参数?/p>
那么Q除了方便外Qؓ什么一定要使用Lambda呢?它比传统的函数或是函数对象有什么好处呢Q我个h所理解的是Q这U函Cq以?#8220;闭包”Q就是因为其限制了别人的讉KQ更U有。也可以认ؓ他是一ơ性的Ҏ。Lambda表达式应该是z的Q极U有的,Z更易的代码和更方便的~程?/p>
在这一节中Q原文主要介l了两个关键?auto ?deltypeQ示例如下: auto 最大的好处是让代码简z,其是那些模板类的声明,比如QSTL中的容器的P代子cd?/p>
可以变成Q?/p>
模板q个Ҏ让C++的代码变得很难读Q不信你可以看看STL的源码,那是一个ؕ啊。用auto必需一个初始化|~译器可以通过q个初始化值推导出cd。因为auto是来化模板类引入的代码难ȝ问题Q如上面的示例,iterationq种cd最适合用auto的,但是Q我们不应该把其滥用?/p>
比如下面的代码的可读性就降低了。因为,我不知道ProcessDataq回什么?int? bool? q是对象Q或是别的什么?q让你后面的E序不知道怎么做?/p>
但是下面的程序就没有问题Q因为pObject的型别在后面的new中有了?/p>
关于 原文l出的示例如下,我们可以看到Q这个让的确我们的定义变量省了很多事?/p>
q有一个适合的用法是用来typedef函数指针Q也会省很多事。比如: Wikipedia 上是q么说的Q关于decltype的规则见上) 如果auto ?decltype 在一起用会是什么样子?能看下面的示例,下面q个CZ也是引入decltype的一个原?#8212;—让C++有能力写一?“ forwarding function 模板”Q?/p>
q个函数模板看v来相当费解,其用Cauto ?decltype 来扩展了已有的模板技术的不。怎么个不_Q在上例中,我不知道AddingFunc会接收什么样cd的对象,q两个对象的 + 操作W返回的cd也不知道Q老的模板函数无法定义AddingFuncq回值和q两个对象相加后的返回值匹配,所以,你可以用上q的q种定义?/p>
C/C++的初始化的方法比较,C++ 11 用大括号l一了这些初始化的方法?/p>
比如QPOD的类型?/p>
关于POD相说两句Q所谓POD是Plain Old DataQ当class/struct?em>极简?trivial)、属?em>标准布局(standard-layout)Q以及他的所有非静态(non-staticQ成员都是PODӞ会被视ؓPOD。如Q?/p>
POD的初始化有点怪,比如上例Qnew A; 和new A(); 是不一LQ对于其内部的mQ前者没有被初始化,后者被初始化了Q不?的编译器行ؓ不一PVC++和GCC不一P。而非POD的初始化Q则都会被初始化?/p>
从这点可以看出,C/C++的初始化问题很奇怪,所以,在C++ 2011版中做了统一。原文作者给Z如下的示例: 容器的初始化Q?/p>
q支持像Java一L成员初始化: 我们知道C++的编译器在你没有定义某些成员函数的时候会l你的类自动生成q些函数Q比如,构造函敎ͼ拯构造,析构函数Q赋值函数。有些时候,我们不想要这些函敎ͼ比如Q构造函敎ͼ因ؓ我们惛_实现单例模式。传l的做法是将其声明成privatecd?/p>
在新的C++中引入了两个指示W,delete意ؓ告诉~译器不自动产生q个函数Qdefault告诉~译器生一个默认的。原文给Z下面两个例子Q?/p>
再如delete q里Q我惌一下,Z么我们需要defaultQ我什么都不写不就是default吗?不全然是Q比如构造函敎ͼ因ؓ只要你定义了一个构造函敎ͼ~译器就不会l你生成一个默认的了。所以,Z要让默认的和自定义的共存Q才引入q个参数Q如下例所C: 关于deleteq有两个有用的地Ҏ 1Q让你的对象只能生成在栈内存上: 2Q阻止函数的其Ş参的cd调用Q(若尝试以 double 的Ş参调?nbsp; C/C++的NULL宏是个被有很多潜在BUG的宏。因为有的库把其定义成整?Q有的定义成 (void*)0。在C的时代还好。但是在C++的时代,q就会引发很多问题。你可以上网看看。这是ؓ什么需?nbsp; 在以前的C++中,构造函C间不能互相调用,所以,我们在写q些怼的构造函数里Q我们会把相同的代码攑ֈ一个私有的成员函数中?/p>
但是Qؓ了方便ƈ不?#8220;委托构?#8221;q个事出玎ͼ最主要的问题是Q基cȝ构造不能直接成为派生类的构造,q是基cȝ构造函数够了,zc还要自己写自己的构造函敎ͼ 上例中,zcL动承基cȝ构造函敎ͼ ~译器可以用基cȝ构造函数完成派生类的构造?而将基类的构造函数带入派生类的动?无法选择性地部分带入Q?所以,要不是l承基类全部的构造函敎ͼ要不是一个都不?不手动带??此外Q若牉|到多重承,从多个基cȝ承而来的构造函C可以有相同的函数{(signature)?而派生类的新加入的构造函C不可以和l承而来的基cL造函数有相同的函数签名,因ؓq相当于重复声明。(所谓函数签名就是函数的参数cd和顺序不Q?/p>
在老版的C++中,临时性变量(UCؓ叛_?#8221;R-values”Q位于赋值操作符之右Q经常用作交换两个变量。比如下面的CZ中的tmp变量。示例中的那个函数需要传递两个string的引用,但是在交换的q程中生了对象的构造,内存的分配还有对象的拯构造等{动作,成本比较高?/p>
C++ 11增加一个新的引用(referenceQ类型称作右值引用(R-value referenceQ,标记?tt>typename &&。他们能够以non-const值的方式传入Q允许对象去改动他们。这修正允许特定对象创造出move语义?/p>
举例而言Q上面那个例子中QstringcM保存了一个动态内存分存的char*指针Q如果一个string对象发生拯构造(如:函数q回Q,stringc里的char*内存只能通过创徏一个新的时对象,q把函数内的对象的内存copy到这个新的对象中Q然后销毁时对象及其内存?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其可以评估括号内表辑ּ的类型,其规则如下:
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&, 因ؓ函数的返回类型是
// std::vector<int>::operator[](size_type) const
auto c = 0;
// c 的类型是 int
auto d = c;
// d 的类型是 int
decltype(c) e;
// e 的类型是 int, 因ؓ c 的类型是int
decltype((c)) f = c;
// f 的类型是 int&, 因ؓ (c) 是左?/code>
decltype(0) g;
// g 的类型是 int, 因ؓ 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将会引发编译期错误Q?~译器不会自动将 double 形参转型?int 再调?code>f()Q如果传入的参数是doubleQ则会出现编译错误)
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。这样一来,使得Ҏ?span style="font-family: monospace">stirng的右值引用可以单U地从右值复制其内部C-style的指针到新的stringQ然后留下空的右倹{这个操作不需要内存数l的复制Q而且I的暂时对象的析构也不会释放内存。其更有效率?/p>
1
2
3
4
5 |
class string { string (string&&); //move constructor string&& operator=(string&&); //move assignment operator }; |
The C++11 STL中广泛地使用了右值引用和move语议。因此,很多法和容器的性能都被优化了?/p>
C++ STL库在2003q经历了很大的整Ҏ?nbsp;Library Technical Report 1 (TR1)?TR1 中出C很多新的容器c?(unordered_set
, unordered_map
, unordered_multiset
, ?nbsp;unordered_multimap
) 以及一些新的库支持诸如Q正则表辑ּQ?tuplesQ函数对象包装,{等?C++11 批准?TR1 成ؓ正式的C++标准Q还有一些TR1 后新加的一些库Q从而成Z新的C++ 11 STL标准库。这个库主要包含下面的功能:
q们׃多说了,以前的STL饱受U程安全的批评。现在好 了。C++ 11 支持U程cM。这涉及两个部分:W一、设计一个可以多个U程在一个进E中共存的内存模型;W二、ؓ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中被废弃了?/code>C++11 引入了两个指针类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>
定义了一些新的算法: 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:
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比如: XMLQsocketsQreflectionQ当然还有垃圑֛收。看来要{到C++ 20了。呵c不qC++ 11在性能上还是很快。参?Google’s benchmark tests。原文还引用Stroustrup 的观点:C++11 是一门新的语a——一个更好的 C++?/p>
如果把所有的改变都列出来Q你会发现真多啊。我估计C++ Primer那本书的厚度要增加至?0%以上。C++的门槛会不会来高了呢Q我不知道,但我个h觉得q门语言的确是变得越来越令h望而却步了。(惌v了某人和我说的一句话——学技术真的是太篏了,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每个函数调用花费了多少旉。还可以昄“注释的源代码”Q是E序源代码的一个复本,标记有程序中每行代码的执行次数?/p>
在编译或链接源程序的时候在~译器的命o行参C加入“-pg”选项Q编译时~译器会自动在目标代码中插入用于性能试的代码片断,q些代码在程序在q行旉集ƈ记录函数的调用关pd调用ơ数Q以及采集ƈ记录函数自n执行旉和子函数的调用时_E序q行l束后,会在E序退出的路径下生成一个gmon.out文g。这个文件就是记录ƈ保存下来的监控数据。可以通过命o行方式的gprof或图形化的Kprof来解读这些数据ƈ对程序的性能q行分析。另外,如果x看库函数的profilingQ需要在~译是再加入“-lc_p”~译参数代替“-lc”~译参数Q这L序会链接libc_p.a库,才可以生库函数的profiling信息。如果想执行一行一行的profilingQ还需要加?#8220;-g”~译参数?br />例如如下命o行: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在函数及其衍生函数的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] [其它参数]
Ҏ号中的内容可以省略。如果省略了“可执行文?#8221;Qgprof会在当前目录下搜索a.out文g作ؓ可执行文Ӟ而如果省略了gmon.out文gQgprof也会在当前目录下Lgmon.out。其它参数可以控制gprof输出内容的格式等信息。最常用的参数如下:
l -b 不再输出l计图表中每个字D늚详细描述?
l -p 只输出函数的调用图(Call graph的那部分信息Q?
l -q 只输出函数的旉消耗列表?
l -e Name 不再输出函数Name 及其子函数的调用图(除非它们有未被限制的其它父函敎ͼ。可以给定多?-e 标志。一?-e 标志只能指定一个函数?
l -E Name 不再输出函数Name 及其子函数的调用图,此标志类g -e 标志Q但它在L间和癑ֈ比时间的计算中排除了由函数Name 及其子函数所用的旉?
l -f Name 输出函数Name 及其子函数的调用图。可以指定多?-f 标志。一?-f 标志只能指定一个函数?
l -F Name 输出函数Name 及其子函数的调用图,它类g -f 标志Q但它在L间和癑ֈ比时间计中仅用所打印的例E的旉。可以指定多?-F 标志。一?-F 标志只能指定一个函数?F 标志覆盖 -E 标志?
l -z 昄使用ơ数为零的例E(按照调用计数和篏U时间计)?
不过,gprof不能昄对象之间的承关p?q也是它的弱?
转蝲来源Q?a >http://blog.csdn.net/dragonbooker/article/details/6173321
大端模式
所谓的大端模式Q是指数据的低位Q就是权D的后面那几位)保存在内存的高地址中,而数据的高位Q保存在内存的低地址中,q样的存储模式有点儿cM于把数据当作字符串顺序处理:地址由小向大增加Q而数据从高位往低位放;
端模式
所谓的端模式Q是指数据的低位保存在内存的低地址中,而数 据的高位保存在内存的高地址中,q种存储模式地址的高低和数据位权有效地结合v来,高地址部分权值高Q低地址部分权gQ和我们的逻辑Ҏ一致?/span>
Z么有大小端模式之?/span>
Z么会有大端模式之分呢?q是因ؓ在计机pȝ中,我们是以字节为单位的Q每个地址单元都对应着一个字节,一个字节ؓ 8bit。但是在C语言中除?/span>8bit?/span>char之外Q还?/span>16bit?/span>short型,32bit?/span>long型(要看具体的编译器Q,另外Q对于位数大?/span> 8位的处理器,例如16位或?/span>32位的处理器,׃寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就D了大端存储模式和端存储模式。例如一?/span>16bit?/span>short?/span>xQ在内存中的地址?/span>0x0010Q?/span>x的gؓ0x1122Q那?/span>0x11为高字节Q?/span>0x22Z字节。对?/span> 大端模式Q就?/span>0x11攑֜低地址中,?/span>0x0010中,0x22攑֜高地址中,?/span>0x0011中。小端模式,刚好相反。我们常用的X86l构是小端模式,?/span>KEIL C51则ؓ大端模式。很多的ARMQ?/span>DSP都ؓ端模式。有?/span>ARM处理器还可以q件来选择是大端模式还是小端模式?/span>
大家都知道字W?#8216;A’?/span>ASCII码gؓ65Q十q制Q也是0x41Q那么这个值在不同大小端模式的pȝ中存攄方式分别为:
大端模式Q?/span>00 00 00 41 -----高低模式
端模式Q?/span>41 00 00 00 -----低低模式
可以通过声明一个联合(unionQ判断大端模式Q?/span>
/**
* Ҏ一 得到当前pȝ的大端属?/span>, 此方法要保证?/span>32位机试
*/
static union {
char c[4];
unsigned long l;
}
endian_test = { { 'l', '?', '?', 'b' } };
#define ENDIANNESS ((char)endian_test.l)
/**
* Ҏ?/span>: 得到当前pȝ的大端属?/span>
*/
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;
}
/**
* Ҏ?/span>: 得到当前pȝ的大端属?/span>
*/
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) // ?/span>c变量所在地址上的一个字节?/span>
{
printf("little endian/n");
}
else
printf("big endian");
return 0;
}
前言
07q?2月,我写了一《C++虚函数表解析》的文章Q引起了大家的兴。有很多朋友Ҏ的文章留了言Q有鼓励我的Q有批评我的Q还有很多问问题的。我在这里一q对大家的留a表示感谢。这也是我ؓ什么再写一箋a的原因。因为,在上一文章中Q我用了的示例都是非常简单的Q主要是Z说明一些机理上的问题,也是Z图一些表达上方便和简单。不惻Iq篇文章成ؓ了打开C++对象模型内存布局的一个引子,引发了大家对C++对象的更深层ơ的讨论。当Ӟ我之前的文章q有很多斚w没有涉及Q从我个人感觉下来,在谈函数表里Q至有以下q些内容没有涉及Q?/p>
1Q有成员变量的情c?/p>
2Q有重复l承的情c?/p>
3Q有虚拟l承的情c?/p>
4Q有ȝ型虚拟承的情况?/p>
q些都是我本文章需要向大家说明的东ѝ所以,q篇文章会是《C++虚函数表解析》的一个箋,也是一高U进阶的文章。我希望大家在读q篇文章之前对C++有一定的基础和了解,q能先读我的上一文章。因文章的深度可能会比较深Q而且会比较杂乱,我希望你在读本篇文章时不会有大脑思维紊ؕD大脑L的情c?-)
对象的媄响因?/p>
而言之,我们一个类可能会有如下的媄响因素:
1Q成员变?/p>
2Q虚函数Q生虚函数表)
3Q单一l承Q只l承于一个类Q?/p>
4Q多重承(l承多个c)
5Q重复承(l承的多个父cM其父cL相同的超c)
6Q虚拟承(使用virtual方式l承Qؓ了保证承后父类的内存布局只会存在一份)
上述的东襉K常是C++q门语言在语义方面对对象内部的媄响因素,当然Q还会有~译器的影响Q比如优化)Q还有字节对齐的影响。在q里我们都不讨论Q我们只讨论C++语言上的影响?/p>
本篇文章着重讨Zq几个情况下的C++对象的内存布局情况?/p>
1Q单一的一般承(带成员变量、虚函数、虚函数覆盖Q?/p>
2Q单一的虚拟承(带成员变量、虚函数、虚函数覆盖Q?/p>
3Q多重承(带成员变量、虚函数、虚函数覆盖Q?/p>
4Q重复多重承(带成员变量、虚函数、虚函数覆盖Q?/p>
5Q钻矛_的虚拟多重承(带成员变量、虚函数、虚函数覆盖Q?/p>
我们的目标就是,让事情越来越复杂?/p>
知识复习
我们单地复习一下,我们可以通过对象的地址来取得虚函数表的地址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方式来取得整个对象实例的内存布局。因些东西在内存中都是连l分布的Q我们只需要用适当的地址偏移量,我们可以获得整个内存对象的布局?/p>
本篇文章中的例程或内存布局主要使用如下~译器和pȝQ?/p>
1QWindows XP ?VC++ 2003
2QCygwin ?G++ 3.4.4
单一的一般?/strong>
下面Q我们假设有如下所C的一个承关p:
h意,在这个承关pMQ父c,子类Q孙子类都有自己的一个成员变量。而了c覆盖了父类的f()ҎQ孙子类覆盖了子cȝg_child()及其类的f()?/p>
我们的源E序如下所C:
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序作ؓ试E序Q(下面E序中,我用了一个int** pVtab 来作为遍历对象内存布局的指针,q样Q我可以方便地像用数l一h遍历所有的成员包括其虚函数表了Q在后面的程序中Q我也是用这LҎ的,请不必感到奇怪,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:Q在VC++ 2003和G++ 3.4.4下)
[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在单一的承中Q被overwrite的虚函数在虚函数表中得到了更新?/p>
多重l承
下面Q再让我们来看看多重l承中的情况Q假设有下面q样一个类的承关pR注意:子类只overwrite了父cȝf()函数Q而还有一个是自己的函敎ͼ我们q样做的目的是ؓ了用g1()作ؓ一个标记来标明子类的虚函数表)。而且每个cM都有一个自q成员变量Q?/p>
我们的类l承的源代码如下所C:父类的成员初始ؓ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注意我使用了一个s变量Q其中用Csizof(Base)来找下一个类的偏U量。(因ؓ我声明的是int成员Q所以是4个字节,所以没有对齐问题。关于内存的寚w问题Q大家可以自行试验,我在q里׃多说了)
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:Q在VC++ 2003和G++ 3.4.4下)
[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 |
使用囄表示是下面这个样子:
我们可以看到Q?/p>
1Q?每个父类都有自己的虚表?/p>
2Q?子类的成员函数被攑ֈ了第一个父cȝ表中?/p>
3Q?内存布局中,其父cd局依次按声明顺序排列?/p>
4Q?每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做是Z解决不同的父cȝ型的指针指向同一个子cd例,而能够调用到实际的函数?/p>
重复l承
下面我们再来看看Q发生重复承的情况。所谓重复承,也就是某个基c被间接地重复承了多次?/p>
下图是一个承图Q我们重载了父类的f()函数?/p>
其类l承的源代码如下所C。其中,每个c都有两个变量,一个是整ŞQ?字节Q,一个是字符Q?字节Q,而且q有自己的虚函数Q自己overwrite父类的虚函数。如子类D中,f()覆盖了超cȝ函数Q?f1() 和f2() 覆盖了其父类的虚函数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:Q在VC++ 2003和G++ 3.4.4下)
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例中的虚函数表的图:
我们可以看见Q最端的父cB其成员变量存在于B1和B2中,q被Dl承下M。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两䆾Q一份是B1l承而来的,另一份是B2l承而来的。所以,如果我们使用以下语句Q则会生二义性编译错误:
D d;
d.ib = 0; //二义性错?/p>
d.B1::ib = 1; //正确
d.B2::ib = 2; //正确
注意Q上面例E中的最后两条语句存取的是两个变量。虽然我们消除了二义性的~译错误Q但BcdD中还是有两个实例Q这U扉K成了数据的重复Q我们叫q种l承为重复ѝ重复的基类数据成员可能q不是我们想要的。所以,C++引入了虚基类的概c?/p>
ȝ型多重虚拟?/strong>
虚拟l承的出现就是ؓ了解决重复承中多个间接父类的问题的。钻矛_的结构是其最l典的结构。也是我们在q里要讨论的l构Q?/p>
上述?#8220;重复l承”只需要把B1和B2l承B的语法中加上virtual 关键Q就成了虚拟l承Q其l承囑֦下所C:
上图和前面的“重复l承”中的cȝ内部数据和接口都是完全一LQ只是我们采用了虚拟l承Q其省略后的源码如下所C:
class B {……};
class B1 : virtual public B{……};
class B2: virtual public B{……};
class D : public B1, public B2{ …… };
在查看D之前Q我们先看一看单一虚拟l承的情c下面是一D在VC++2003下的试E序Q(因ؓVC++和GCC的内存而局上有一些细节上的不同,所以这里只l出VC++的程序,GCC下的E序大家可以Ҏ我给出的E序自己仿照着写一个去试一试)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 = ";
其运行结果如下(我结ZGCC的和VC++2003的对比)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我会在后面一q再说?/p>
下面的测试程序是看子cD的内存布局Q同hVC++ 2003的(因ؓVC++和GCC的内存布局上有一些细节上的不同,而VC++的相对要清楚很多Q所以这里只l出VC++的程序,GCC下的E序大家可以Ҏ我给出的E序自己仿照着写一个去试一试)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两部份)
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了(前面的作囑ַl让我生了很严重的厌倦感Q所以就偷个懒了Q大家见谅了Q?/p>
在上面的输出l果中,我用不同的颜色做了一些标明。我们可以看到如下的几点Q?/p>
1Q无论是GCCq是VC++Q除了一些细节上的不同,其大体上的对象布局是一L。也是_先是B1Q黄ԌQ然后是B2Q绿ԌQ接着是DQ灰ԌQ而Bq个类Q青蓝色Q的实例都放在最后的位置?/p>
2Q关于虚函数表,其是第一个虚表,GCC和VC++有很重大的不一栗但仔细看下来,q是VC++的虚表比较清晰和有逻辑性?/p>
3QVC++和GCC都把Bq个类攑ֈ了最后,而VC++有一个NULL分隔W把B和B1和B2的布局分开。GCC则没有?/p>
4QVC++中的内存布局有两个地址我有些不是很明白Q在其中我用U色标出了。取其内Ҏ-4。接道理来说Q这个指针应该是指向Bcd例的内存地址Q这个做法就是ؓ了保证重复的父类只有一个实例的技术)。但取值后却不是。这Ҏ目前qƈ不太清楚Q还向大家请教?/p>
5QGCC的内存布局中在B1和B2中则没有指向B的指针。这点可以理解,~译器可以通过计算B1和B2的size而得出B的偏U量?/p>
l束?/strong>
C++q门语言是一门比较复杂的语言Q对于程序员来说Q我们似乎永q摸不清楚这门语a背着我们在干了什么。需要熟悉这门语aQ我们就必需要了解C++里面的那些东西,需要我们去了解他后面的内存对象。这h们才能真正的了解C++Q从而能够更好的使用C++q门最隄~程语言?/p>
在文章束之前q是介绍一下自己吧。我从事软g研发有十个年头了Q目前是软g开发技术主,技术方面,LUnix/C/C++Q比较喜Ƣ网l上的技术,比如分布式计,|格计算QP2PQAjax{一切和互联|相关的东西。管理方面比较擅长于团队Q技术趋势分析,目理。欢q大家和我交,我的MSN和Email是:haoel@hotmail.com