从汇编看c++初始化列表初始化成员变量
简略来说,编译器会对初始化列表按照成员变量的声明顺序重新一一排序,安插到构造函数中进行初始化操作,而且这些初始化操作在构造函数里面用户自己定义的任何代码之前。
下面是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++初始化列表初始化成员变量的更多相关文章
- Effective C++学习笔记:初始化列表中成员列出的顺序和它们在类中声明的顺序相同
类成员的默认初始化顺序是按照声明顺序进行, 如果使用初始化列表初始化成员变量, 则必须按照成员变量的声明顺序进行; 否则, 在变量之间交替赋值时, 会产生, 未初始化的变量去赋值其他变量; 同时GCC ...
- 【c++】构造函数初始化列表中成员初始化的次序性
上代码 #include <iostream> using namespace std; class A { public: A(int v): j(v + 2), i(j) {} voi ...
- C++:只用初始化列表初始化变量的几种情况
1.类成员函数中const变量的初始化(也就是第一点) 有几个容易混淆的地方: (1)const 的变量只能通过构造函数的初始化列表进行初始化:(貌似在C++11中可以正常编译) (2)static ...
- 默认初始化&拷贝初始化&直接初始化&值初始化&列表初始化
一.各种初始化的形式 /* 定义变量形式一:不指定初始值 */ int a; // 默认初始化 /* 定义变量形式二:指定初始值 */ int b = 1; // 拷贝初始化 int b(1); // ...
- java初始化过程中成员变量
package day01; class Base{ int j; //1.j=0 Base(){ add(1); //2.调用子类add()方法 System.out.println(j); //4 ...
- c++ 初始化静态static成员变量或static复合成员变量
https://stackoverflow.com/questions/185844/how-to-initialize-private-static-members-in-c https://sta ...
- C++中初始化列表的使用(总结)
原文链接 https://www.cnblogs.com/dishengAndziyu/p/10906081.html 参考链接:https://www.cnblogs.com/laiqun/p/57 ...
- c++——初始化列表
多个对象构造和析构 1对象初始化列表 1)对象初始化列表出现原因 1.必须这样做: 如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数.这时要 ...
- C++解析(12):初始化列表与对象构造顺序、析构顺序
0.目录 1.类成员的初始化 2.类中的const成员 3.对象的构造顺序 3.1 局部对象的构造顺序 3.2 堆对象的构造顺序 3.3 全局对象的构造顺序 4.对象的析构顺序 5.小结 1.类成员的 ...
随机推荐
- (转)在Eclipse中使用JUnit4进行单元测试
原地址:http://blog.csdn.net/andycpp/article/details/1327147
- hdu3830 (二分+LCA)
转载请注明出处: http://www.cnblogs.com/fraud/ ——by fraud Checkers Time Limit: 2000/1000 MS (Java/O ...
- Js之Screen对象
Window Screen window.screen 对象在编写时可以不使用 window 这个前缀. 属性: screen.availWidth - 可用的屏幕宽度,以像素计,减去界面特性,比如窗 ...
- JSON序列化选项
JSON.stringify()除了接受序列化js对象外,还可以接受另外的两个参数,这两个参数用于指定使用什么样的方式序列化js对象. 第一个参数是个过滤器,可以一个数组或者一个函数:第二个参数是一个 ...
- Tomcat学习笔记 - 错误日志 - Tomcat访问Manager apps出现401 Unauthorized错误
原因是配置文件中未指定管理员身份. 打开tomcat>conf>tomcat-user.xml文件,添加如下代码: <role rolename="admin-gui&qu ...
- python运维开发(二十二)---JSONP、瀑布流、组合搜索、多级评论、tornado框架简介
内容目录: JSONP应用 瀑布流布局 组合搜索 多级评论 tornado框架简介 JSONP应用 由于浏览器存在同源策略机制,同源策略阻止从一个源加载的文档或脚本获取或设置另一个源加载的文档的属性. ...
- wine下汉字方块解决
1.修改字体 首先,下载一个字体,例如win下的新宋体 其次, mkdir /usr/share/fonts/windows mv simsun.ttc /usr/share/fonts/window ...
- postgresql赋予/撤消 用户权限
(1)给予权限:grant grant select on 表名 to 用户名: (2)撤消权限:revoke revoke select on 表名 from ...
- SMC MCU
Holtek推出e-Banking智能卡读卡器MCU——HT56RU25,继HT56RB27.HT56RB688 USB接口单片机之后,推出全新UART接口单片机.HT56RU25内建ISO7816- ...
- 有意思的GacUI
所有方法,无论是你写还是工具来codegen还是用宏,最终都指向把这些名字和对应的指针存在一个map里.C++是不提供这个功能的,我也没仔细研究过qt怎么做,不过我在我自己的gacui里面实现了类似的 ...