简略来说,编译器会对初始化列表按照成员变量的声明顺序重新一一排序,安插到构造函数中进行初始化操作,而且这些初始化操作在构造函数里面用户自己定义的任何代码之前。

下面是c++源码:

class X {
private:
int i;
int j;
int k;
int l;
public:
X() : j(), i(), l() {
k = ;
}
}; int main() {
X x;
}

下面是main函数里面的汇编码:

; 13   : int main() {

    push    ebp
mov ebp, esp
sub esp, ; 为对象x预留16byte的空间 ; 14 : X x; lea ecx, DWORD PTR _x$[ebp];将对象x的首地址传递给寄存器ecx,作为隐含参数传递给构造函数(即this指针)
call ??0X@@QAE@XZ ; 调用构造函数 ; 15 : } xor eax, eax
mov esp, ebp
pop ebp
ret
_main ENDP

下面是构造函数的汇编码:

??0X@@QAE@XZ PROC                    ; X::X, COMDAT
; _this$ = ecx ; 8 : X() : j(1), i(2), l(3) { push ebp
mov ebp, esp
push ecx;将寄存器ecx压栈的目的是为保留对象首地址预留空间
mov DWORD PTR _this$[ebp], ecx;将对象首地址存到刚才预留的空间里面
mov eax, DWORD PTR _this$[ebp];将对象首地址给寄存器eax
mov DWORD PTR [eax], ;将2写入对象首地址所指内存,即将2赋给成员变量i
mov ecx, DWORD PTR _this$[ebp];将对象首地址给寄存器ecx
mov DWORD PTR [ecx+], ;将1赋给偏移对象首地址4byte处内存,即将1赋给成员变量j
mov edx, DWORD PTR _this$[ebp];将对象首地址给edx寄存器
mov DWORD PTR [edx+], ;将3写入偏移对象首地址12byte处,即将3赋给成员变量l ; 9 : k = 4; mov eax, DWORD PTR _this$[ebp];将对象首地址给寄存器eax
mov DWORD PTR [eax+], ;将4写入偏移对象首地址8byte处,即将4赋给成员变量k ; 10 : } mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret
??0X@@QAE@XZ ENDP

从汇编吗可以看到,在初始化列表中,虽然j排在i的前面(i的声明在j前),但是,编译器插入到构造函数里面的初始化操作仍然将i的初始化排在了j的前面。并且编译器插入到构造函数里面的初始化操作都在构造函数里面原有初始化操作(初始化k)的前面,尽管l的声明顺序比k的声明顺序晚。

如果不注意声明顺序和初始化列表顺序的这种关系,可能会导致下面的问题:

class X {
private:
int i;
int j;
public:
X() : j(), i(j) {
}
}; int main() {
X x;
}

通过上面的分析可以知道,i先初始化,但这时候初始化i的时候j还没初值,所以i的值无法预知。

有4中情况必须使用初始化列表:

1 当初始化一个引用成员时

2 当初始化一个const成员时

3 当调用基类的构造函数,而它拥有参数时

4 当调用成员变量的构造函数,而它拥有参数时

下面来看一种情形:

先来看c++源码:

class X {
private:
int i;
public:
X(int ii) {
i = ii;
}
X() {}
};
class Y {
private:
int j;
X x;
public:
Y() {
j = ;
x = X();
}
}; int main() {
Y y;
}

接下来是main函数中的汇编码:

_main    PROC

; 21   : int main() {

    push    ebp
mov ebp, esp
sub esp, ;为对象y预留8byte空间 ; 22 : Y y; lea ecx, DWORD PTR _y$[ebp];将y对象首地址传递给寄存器ecx,作为隐含参数传递给构造函数
call ??0Y@@QAE@XZ ; 调用对象y的构造函数 ; 23 : } xor eax, eax
mov esp, ebp
pop ebp
ret
_main ENDP

下面主要来看对象y的构造函数汇编码:

??0Y@@QAE@XZ PROC                    ; Y::Y, COMDAT
; _this$ = ecx ; 15 : Y() { push ebp
mov ebp, esp
sub esp, ;为保留函数中使用到的变量预留空间,这里主要是this指针和临时对象
mov DWORD PTR _this$[ebp], ecx;将对象y的首地址保留到刚才预留空间
mov ecx, DWORD PTR _this$[ebp];将对象首地址给寄存器ecx
add ecx, ;对象首地址加4,此时ecx中存储的是对象y的成员对象x的首地址,它将作为隐含参数传递给成员对象x的构造函数
call ??0X@@QAE@XZ ; 调用成员对象x的默认构造函数
;在执行对象y构造函数里面的代码之前,调用成员对象的构造函数
;这里,如果成员对象x没有默认构造函数,将出错 ; 16 : j = 1; mov eax, DWORD PTR _this$[ebp];将对象y首地址给eax寄存器
mov DWORD PTR [eax], ;将1写入y首地址处内存,寄给成员变量j赋值1 ; 17 : x = X(2); push ;将2压栈,作为参数传递给成员对象x的构造函数
lea ecx, DWORD PTR $T2568[ebp];将临时对象的首地址给ecx寄存器,作为隐含参数传递给类X的构造函数
call ??0X@@QAE@H@Z ; 调用类X的构造函数,有参数
mov ecx, DWORD PTR [eax];eax寄存器中保留的是临时对象的首地址,这里将临时对象首地址值处内存内容给ecx寄存器,
;即将临时对象成员变量i值给寄存器ecx
mov edx, DWORD PTR _this$[ebp];将对象y的首地址给edx寄存器
mov DWORD PTR [edx+], ecx;将ecx寄存里面的内容给偏移对象y首地址4byte处内存
;即将临时对象成员变量值拷贝到成员对象x
;这相当于(operate=的拷贝功能) ; 18 : } mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret
??0Y@@QAE@XZ ENDP

可以看到,在对象y的构造函数里面,在执行对象y的构造函数里面任何赋值操作之前,首先调用了成员对象x的默认构造函数,接下来产生了一个临时对象,然后将临时对象在拷贝给成员对象x。这里可以看到,初始化成员对象x的操作是调用默认构造函数来完成的,对象y的构造函数里面的赋值操作只是一个拷贝过程。

下面将成员对象的初始化放到初始化列表中,并且去掉其默认构造函数

先看c++源码

class X {
private:
int i;
public:
X(int ii) {
i = ii;
} };
class Y {
private:
int j;
X x;
public:
Y() : x() {
j = ;
}
}; int main() {
Y y;
}

下面看main函数汇编码:

_main    PROC

; 20   : int main() {

    push    ebp
mov ebp, esp
sub esp, ;为对象y预留8byte空间 ; 21 : Y y; lea ecx, DWORD PTR _y$[ebp];将对象y的首地址给ecx寄存器,作为隐含参数传递给构造函数
call ??0Y@@QAE@XZ ; 调用对象y的构造函数 ; 22 : } xor eax, eax
mov esp, ebp
pop ebp
ret
_main ENDP

下面主要看对象y的构造函数汇编码:

??0Y@@QAE@XZ PROC                    ; Y::Y, COMDAT
; _this$ = ecx ; 15 : Y() : x(2) { push ebp
mov ebp, esp
push ecx;压栈的目的是为了保留对象y的首地址预留空间
mov DWORD PTR _this$[ebp], ecx;将对象y的首地址存到刚才预留的空间
push ;将2压栈,作为参数传递给成员对象x的构造函数
mov ecx, DWORD PTR _this$[ebp];将对象y的首地址给ecx寄存器
add ecx, ;将对象y的首地址加4,得到的是成员对象x的首地址,作为隐含参数传递给成员对象x的构造函数
call ??0X@@QAE@H@Z ; 调用x的构造函数,有参数 ; 16 : j = 1; mov eax, DWORD PTR _this$[ebp];将对象y的首地址给寄存器eax
mov DWORD PTR [eax], ;将1写入对象y首地址处内存,即将1赋给对象y的成员变量j ; 17 : } mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret
??0Y@@QAE@XZ ENDP

可以看到,和上面的最大不同是,即使类X没有默认构造函数,编译器也没有报错,并且,初始化成员对象的操作由有参数的构造函数完成,当中并没有产生临时对象,效率高。

从汇编看c++初始化列表初始化成员变量的更多相关文章

  1. Effective C++学习笔记:初始化列表中成员列出的顺序和它们在类中声明的顺序相同

    类成员的默认初始化顺序是按照声明顺序进行, 如果使用初始化列表初始化成员变量, 则必须按照成员变量的声明顺序进行; 否则, 在变量之间交替赋值时, 会产生, 未初始化的变量去赋值其他变量; 同时GCC ...

  2. 【c++】构造函数初始化列表中成员初始化的次序性

    上代码 #include <iostream> using namespace std; class A { public: A(int v): j(v + 2), i(j) {} voi ...

  3. C++:只用初始化列表初始化变量的几种情况

    1.类成员函数中const变量的初始化(也就是第一点) 有几个容易混淆的地方: (1)const 的变量只能通过构造函数的初始化列表进行初始化:(貌似在C++11中可以正常编译) (2)static ...

  4. 默认初始化&拷贝初始化&直接初始化&值初始化&列表初始化

    一.各种初始化的形式 /* 定义变量形式一:不指定初始值 */ int a; // 默认初始化 /* 定义变量形式二:指定初始值 */ int b = 1; // 拷贝初始化 int b(1); // ...

  5. java初始化过程中成员变量

    package day01; class Base{ int j; //1.j=0 Base(){ add(1); //2.调用子类add()方法 System.out.println(j); //4 ...

  6. c++ 初始化静态static成员变量或static复合成员变量

    https://stackoverflow.com/questions/185844/how-to-initialize-private-static-members-in-c https://sta ...

  7. C++中初始化列表的使用(总结)

    原文链接 https://www.cnblogs.com/dishengAndziyu/p/10906081.html 参考链接:https://www.cnblogs.com/laiqun/p/57 ...

  8. c++——初始化列表

    多个对象构造和析构 1对象初始化列表 1)对象初始化列表出现原因 1.必须这样做: 如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数.这时要 ...

  9. C++解析(12):初始化列表与对象构造顺序、析构顺序

    0.目录 1.类成员的初始化 2.类中的const成员 3.对象的构造顺序 3.1 局部对象的构造顺序 3.2 堆对象的构造顺序 3.3 全局对象的构造顺序 4.对象的析构顺序 5.小结 1.类成员的 ...

随机推荐

  1. XML 解析中,如何排除控制字符

    XML 解析中,如何排除控制字符 今天在解析一个中文的 XML时,始终报错 PCDATA invalid Char value 21 in Entity ,查询了一下这个 21 的ascii 值,发现 ...

  2. Lucene学习总结之三:Lucene的索引文件格式(1)

    Lucene的索引里面存了些什么,如何存放的,也即Lucene的索引文件格式,是读懂Lucene源代码的一把钥匙. 当我们真正进入到Lucene源代码之中的时候,我们会发现: Lucene的索引过程, ...

  3. 从Ecipse中导出程序至apk

    若未有数字证书: 1. 2. 3. 4. 5. 若已有数字证书: 上面的后3步改为

  4. Js 导出Excel IE ActiveX控件

    function ExportExcel() { var oXL = new ActiveXObject("Excel.Application"); //创建excel应用程序对象 ...

  5. GTW likes gt(BC 模拟 or 优先队列)

    GTW likes gt Accepts: 54 Submissions: 782 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 13107 ...

  6. Ultra-QuickSort(树状数组+离散化)

    Ultra-QuickSort  POJ 2299 Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 50495   Accep ...

  7. 用fiddler测试ip轮询

    测试业务: 服务端根据域名配置了三台服务器ip,测试ip轮询的逻辑 测试方法: 使用fiddler配置hosts即可 1.1.1.1 第一台ip 1.1.1.1 第二台ip 1.1.1.1 第三台ip ...

  8. centos6.4x64安装vncserver

    参考文章:http://blog.csdn.net/mchdba/article/details/43058849 主要是远程安装oracle11g需要用到这个东西: 进入系统依次执行下列命令: #查 ...

  9. 美国政府关于Google公司2013年度的财务报表红头文件

    请管理员移至新闻版块,谢谢! 来源:http://www.sec.gov/ 财务报表下载↓ 此文仅作参考分析. 10-K 1 goog2013123110-k.htm FORM 10-K   UNIT ...

  10. C# 新特性_协变与逆变 (.net 4.0)

    C#4.0中有一个新特性:协变与逆变.可能很多人在开发过程中不常用到,但是深入的了解他们,肯定是有好处的. 协变和逆变体现在泛型的接口和委托上面,也就是对泛型参数的声明,可以声明为协变,或者逆变.什么 ...