从汇编看c++对静态成员的存取
c++中静态成员变量不存在于对象之中,而存在于全局数据段,只是其可见性受到限制,仅能被所属类访问,而非静态成员变量存在于对象中,
因而,在访问两种不同数据成员时,会有些许差别。
对于静态数据成员的访问,是直接操作其所在内存;对于非静态数据成员,则是由对象首地址 + 成员变量相对于对象首地址的偏移量来访问(对涉及到虚拟继承可能更复杂),有一定的间接性。
下面看c++源码:
class X {
public:
static int x1;
int x2;
int x3;
};
int X::x1 = ;
int main() {
X x;
X* xp = &x;
x.x1 = ;
xp->x1 = ;
X::x1 = ;
x.x3 = ;
xp->x3 = ;
}
其中静态成员变量分了三种方式存取。
下面是main函数对应的汇编码:
; 10 : int main() {
push ebp
mov ebp, esp
sub esp, ; 为变量预留12byte空间,8byte给对象x 4byte给指针xp
; 11 : X x;
; 12 : X* xp = &x;
lea eax, DWORD PTR _x$[ebp];获取对象首地址,存入寄存器eax
mov DWORD PTR _xp$[ebp], eax;将对象首地址给指针xp
; 13 : x.x1 = 1;
mov DWORD PTR ?x1@X@@2HA, ; 将1赋给?x1@X@@2HA(即静态成员变量x1所在内存)所处的内存
; 14 : xp->x1 = 2;
mov DWORD PTR ?x1@X@@2HA, ; 将2赋给?x1@X@@2HA(即静态成员变量x1所在内存)所处的内存
; 15 : X::x1 = 3;
mov DWORD PTR ?x1@X@@2HA, ; 将3赋给?x1@X@@2HA(即静态成员变量x1所在内存)所处的内存
; 16 :
; 17 : x.x3 = 1;
mov DWORD PTR _x$[ebp+], ;将1赋给偏移变量首地址4byte处内存,即将1赋给成员变量x3
; 18 : xp->x3 = 2;
mov ecx, DWORD PTR _xp$[ebp];将指针xp存的值(对象首地址)给寄存器ecx
mov DWORD PTR [ecx+], ;将2赋给偏移变量首地址4byte处内存,即将2赋给成员变量x3
;对于非静态成员变量,访问的方式为对象首地址 + 成员变量相对于对象首地址的偏移量来完成
;而不是想静态成员变量一样,直接操作其内存
; 19 : }
xor eax, eax
mov esp, ebp
pop ebp
ret
_main ENDP
可以看到,静态成员变量三种存取方式一样,直接操作其所在内存,而非静态数据成员要通过对象首地址和偏移量来访问。正式因为静态成员不存在于对象之中,因此,
对于所有的对象,都只有一份实体,不管该静态变量是继承而来(甚至是虚拟继承),存取都是直接操作其内存
下面是非虚拟的继承情形:
下面是c++源码:
class X {
public:
static int x;
int i;
};
class Y : public X {};
int Y::x = ;
int main() {
Y y;
Y* yp = &y;
y.x = ;
yp->x = ;
Y::x = ;
}
下面是main函数的汇编码:
; 10 : int main() {
push ebp
mov ebp, esp
push ebp
mov ebp, esp
sub esp, ;为变量预留8byte 4byte给对象y 4字节给指针变量yp
; 11 : Y y;
; 12 : Y* yp = &y;
lea eax, DWORD PTR _y$[ebp];将对象y的首地址给寄存器eax
mov DWORD PTR _yp$[ebp], eax;将对象首地址给指针yp
; 13 : y.x = 1;
mov DWORD PTR ?x@X@@2HA, ; 将1赋给?x@X@@2HA(即静态成员变量x所在内存)所代表的内存
; 14 : yp->x = 2;
mov DWORD PTR ?x@X@@2HA, ; 将2赋给?x@X@@2HA(即静态成员变量x所在内存)所代表的内存
; 15 : Y::x = 3;
mov DWORD PTR ?x@X@@2HA, ; 将3赋给?x@X@@2HA(即静态成员变量x所在内存)所代表的内存
;可以看到,虽然静态成员变量对于对象y来说是继承而来,但是
;对于其操作认识直接访问内存地址
; 16 : }
xor eax, eax
mov esp, ebp
pop ebp
ret
_main ENDP
可以看到,即使静态成员变量x是对象y继承而来,存取方式也没有变化。
下面来看虚拟继承的情形:
下面是c++源代码:
class X {
public:
static int x;
int i;
};
class Y : virtual public X {};
int Y::x = ;
int main() {
Y y;
Y* yp = &y;
y.x = ;
yp->x = ;
Y::x = ;
}
下面主要是main函数的汇编码:
; 12 : int main() {
push ebp
mov ebp, esp
sub esp, ;为变量预留12byte空间 8byte给对象y(由于虚拟继承的原因,对象y除了包含继承自虚基类X的成员变量i
;外,还有一个额外增加的指针变量) 4byte给指针变量yp
; 13 : Y y;
push ;压入标志,在虚拟继承中有用,目的是为了防止重复构造虚基类子对象(比如菱形继承)
lea ecx, DWORD PTR _y$[ebp];将对象y的首地址给寄存器ecx,作为隐含参数传递给对象y的构造函数
call ??0Y@@QAE@XZ;调用对象y的构造函数
; 14 : Y* yp = &y;
lea eax, DWORD PTR _y$[ebp];将对象y的首地址给寄存器eax
mov DWORD PTR _yp$[ebp], eax;将对象首地址给指针变量yp
; 15 : y.x = 1;
mov DWORD PTR ?x@X@@2HA, ; 将1赋给?x@X@@2HA(即静态成员变量x所在内存)处内存
; 16 : yp->x = 2;
mov DWORD PTR ?x@X@@2HA, ; 将2赋给?x@X@@2HA(即静态成员变量x所在内存)处内存
; 17 : Y::x = 3;
mov DWORD PTR ?x@X@@2HA, ; 将3赋给?x@X@@2HA(即静态成员变量x所在内存)处内存
; 18 : }
xor eax, eax
mov esp, ebp
pop ebp
ret
_main ENDP
可以看到,由于虚拟继承的特殊性,编译器插入了一些特殊操作,并且为对象y提供了非无用的默认构造函数,但是,对于静态成员变量的存取方式, 仍然未变。
通过上面的分析,可以知道,取一个静态成员变量的地址和取一个非静态成员变量的地址是不一样的,前者取到的是一个真正的指针,后者则是一个 指向对象成员的指针,其值并不是一个地址,而是偏移量。
下面通过c++程序来验证:
#include <iostream>
#include <cstdio>
#include <typeinfo>
using namespace std; class X {
public:
static int i;
int j;
int k;
};
int X::i = ; int main() {
cout << typeid(&X::i).name() << endl;//取静态成员变量地址
cout << typeid(&X::k).name() << endl;//取非静态成员变量地址
printf("&X::i = %d\n", &X::i);//输出静态成员变量地址
printf("&X::k = %d\n", &X::k);//输出非静态成员变量地址 不能用cout,否则得不到正确结果
}
运行结果:
从汇编看c++对静态成员的存取的更多相关文章
- 从汇编看c++成员函数指针(三)
前面的从汇编看c++中成员函数指针(一)和从汇编看c++成员函数指针(二)讨论的要么是单一类,要么是普通的多重继承,没有讨论虚拟继承,下面就来看一看,当引入虚拟继承之后,成员函数指针会有什么变化. 下 ...
- 从汇编看c++中指向成员变量的指针(二)
在从汇编看c++中指向成员变量的指针(一)中讨论的情形没有虚拟继承,下面来看看,当加入了虚拟继承的时候,指向成员变量的指针有什么变化. 下面是c++源码: #include <iostream& ...
- 从汇编看c++成员函数指针(二)
下面先看一段c++源码: #include <cstdio> using namespace std; class X { public: virtual int get1() { ; } ...
- 从汇编看c++中成员函数指针(一)
下面先来看c++的源码: #include <cstdio> using namespace std; class X { public: int get1() { ; } virtual ...
- 从汇编看c++多重继承中this指针的变化
先来看一下下面的c++源码: #include <iostream> using namespace std; class X { public: virtual void print1( ...
- 从汇编看c++中指向成员变量的指针(一)
在c++中,指向类成员变量的指针存储的并不是该成员变量所在内存的地址,而仅仅是该成员变量在该类对象中相对于对象首地址的偏移量.因此,它必须绑定到某一个对象或者对象指针上面,这里的对象和对象指针,就相当 ...
- 从汇编看c++中的虚拟继承及内存布局(二)
下面是c++源码: class Top {//虚基类 public: int i; Top(int ii) { i = ii; } virtual int getTop() { cout <&l ...
- 从汇编看c++的虚拟继承以及其内存布局(一)
先看第一种最简单的情形,所有类中没有任何虚函数的菱形继承. 下面是c++源码: class Top {//虚基类 public: int i; Top(int ii) { i = ii; } }; c ...
- 从汇编看c++初始化列表初始化成员变量
简略来说,编译器会对初始化列表按照成员变量的声明顺序重新一一排序,安插到构造函数中进行初始化操作,而且这些初始化操作在构造函数里面用户自己定义的任何代码之前. 下面是c++源码: class X { ...
随机推荐
- Oracle相关的知识点
1. 如何在Oracle SQLPlus中执行SQL Script文件 以下面的格式在提示符中输入@{file name} SQL>@{file} 假设你要运行的文件的名字是script.sql ...
- GitHub+hexo to Blog
title: GitHub+hexo to Blog date: 2014-12-26 09:44:53 tags: hexo, github --- 摘要 一直想要一个自己的博客,不过一直怯于对网站 ...
- linux cat命令的<<EOF
初初开始学习linux的命令,只对linux一些简单命令有一些了解! 首先我看到网上有一些创建一个文件采用的命令是(mkdir创建文件夹):cat > test1.txt <<EOF ...
- linux访问windows共享文件夹的两种方法
有时需要在linux下需要访问windows的共享文件夹,可以使用mount挂载或者使用samba连接. 1.mount挂载 首先创建被挂载的目录: $ mkdir windows 将共享文件夹挂载到 ...
- CentOS6下编译安装Python2.7.6方法
关于在CentOS6下编译安装Python2.7.6的方法非常的多了,小编以前也介绍过相关的文章了,下面一聚教程小编再来为各位介绍一下吧,希望文章能帮助到各位. CentOS下面Python在升级 ...
- javascript get获取参数
function GetQueryString(name) { var reg = new RegExp("(^|&)"+ name +"=([^&]*) ...
- 视听说加速器--AHK辅助工具
大学有视听说这门课,看起来这门课设计得非好,可是对大多数人来讲却不能按时完成.到了最后都要抓紧提速,上网找答案,辛苦的抄,有“聪明者”便找加速器来做. 我也是赶着做的人之一.抄答案太累,加速器太卡,还 ...
- 【转】WPF - 第三方控件
WPF - 第三方控件 目前第三方控件在网上形成巨大的共享资源,其中包括收费的也有免费的,有开源的也有不开源的,合理的使用第三方控件将使项目组的工作事半功倍.比如项目中有些复杂的业务逻辑.有些绚丽的效 ...
- 汇编写函数:关于PUBLIC和EXTRN的区别
PUBLIC伪指令的格式:PUBLIC 标识符,标识符... 该伪指令告诉汇编程序放在PUBLIC之后的标识符(本模块的定义的)可为其他模块使用,这些标识符可以是变量.标号或者过程名.言外之意,它不仅 ...
- 活生生的例子:qInstallMessageHandler接受指定类型的函数指针,这样就可以随心所欲的让程序员自定义函数名以及函数位置
理论:qInstallMessageHandler是被定义在全局空间里: http://doc.qt.io/qt-5/qtglobal.html#QtMessageHandler-typedef 实践 ...