VC++中的类的内存分布(上)(通过强制转换,观察地址,以及地址里的值来判断)
0.序
目前正在学习C++中,对于C++的类及其类的实现原理也挺感兴趣。于是打算通过观察类在内存中的分布更好地理解类的实现。因为其实类的分布是由编译器决定的,而本次试验使用的编译器为VS2015 RC,其编译环境为VC++,这里感谢@shenzhigang 提醒。所以此处的标题为《VC++中的类的内存分布》。因为博主可能比较懒,所以把这个知识点分作两次写。( ╯□╰ )。
1.对无虚函数类的探索
1.1 空类
我们先一步一步慢慢来,从一个空的类开始。
//空类
class test
{
};
int main(int argc, char *argv[])
{
test ts;
cout << sizeof(ts) << endl;
return 0;
}
结果输出的是1。
于是我们推测,对于一个空类,内存总会为其分配一个字节的空间。为此,我们可以来验证一下:

int main(int argc, char *argv[])
{
test ts;
char ch = '0';
int a1, a2;
a1 = (int)(&ts);
a2 = (int)(&ts + 1);
memcpy(&ts, &ch, 1);
cout << sizeof(ts) << endl;
return 0;
}


可以看到,a1为ts的地址(强制转化为int),a2为ts的下一个地址,然后我们去内存那里看一下

结果真的把ch里面的内容写入到ts中了。
综上可以得出一个结论,对于一个空类,编译器总会为其分配一个字节的内存。
1.2 仅含数据成员的类
首先对于数据成员为一个字节(char)的类,通过上述的测试代码,结果和空类一样,编译器分配了一个字节的空间给了类中的char。这是在我们的预料之内的事。
可是当我们的类设计成含有不同类型的数据结构的时候,结果就不同了:
class test
{
public:
char c;
int i;
};

程序的输出结果是8。可以看到,此时类占用内存的空间是8个字节。
这就涉及到“内存对齐”了。所以接下来我们就先来探讨一下C++里的“内存对齐”。
内存对齐:
对于一个类(结构体),编译器为了提高内存读取速率以及可移植性,存在一种称作为“内存对齐”的规则。一般对于内存对齐,编译器会帮你完成,但是这种工作其实是可以由编程者自己完成的。
C++中,可以使用#pragma pack(n)的预编译处理进行设置“对齐系数”。(这个对齐系数在VC中一般默认为8。)
为了能够更好地了解内存中内存对齐的流程,我特地画了分配空间的流程图。(仅本人自己理解,如有谬误请各位大侠指出。)

下面我们通过实例来说明一下内存对齐。(在这里先只考虑数据成员不为类(结构体)的情况)

class test
{
public:
char c;
int i;
short s;
};

虽然类的内容一样,但是会因为对齐系数n的不同,内存中的分配也会有所不同。下图能够比较形象地说明,其中,红色表示char型,蓝色标识int型,绿色表示short型。图中的列数是根据min(max(结构体中的数据类型),n)确定的。
(1)例子1:pragma pack(1)

(2)例子2:pragma pack(2)

(3)例子3:pragma pack(4)

可以看到,不同的对齐系数会使内存的分布呈现不同的格局。
讨论完内存对齐之后,我们来看一看类中的static成员。
类中的static成员:
我们设计一个这样的类,类中包括有静态数据成员。

class test
{
public:
static int si;
char c;
int i;
short s;
};

结果我们发现,该类的大小还是和之前无异。
同时,我们通过cout语句查看类中的static成员地址。
cout << sizeof(ts) << '\n' << (int)&ts.si << '\n' << (int)(&ts) << endl;

结果得出的类的地址和类的static成员的地址相差十万八千里。显而易见,类中的static成员并不是和类储存在一起的。
综上可以得到的结论是:类中的成员数据中,仅有非static成员数据才会为其开辟内存空间。
1.3 包含成员函数的类
这里要先感谢一下@melonstreet 提到的问题,已改正。
对于类中的成员函数,我们知道,对于所有类,每个成员函数都只有一个副本。函数代码是存储在对象空间之外的。如果对同一个类定义了10个对象,这些对象的成员函数对应的是同一个函数代码段,而不是10个不同的函数代码段。在这里,关于具体到对象的成员函数是如何调用的,我们有两种猜想:第一种猜想是每一个对象中都必须开辟一段内存,用来存储指向类中的成员函数的指针,每次当外部对对象的成员函数进行调用的时候,通过访问对象空间中的函数指针从而访问函数。第二种猜想是类中的成员函数是独立出来的,每个对象中并没有储存成员函数的相关信息,而成员函数的调用是通过编译器在编译的时候自动帮我们选择要访问的函数。为此,我们也要进行一些测试。

class test
{
public:
static int si;
char c;
int i;
short s;
test():c('0'),i(0),s(0) {};
void print(void) { cout << sizeof(test) << '\n' << (int)&si << '\n' << (int)this << endl; }
};


输出结果显示内存仍然不变,说明成员函数在类的内存中并不占空间。
综上可以得出结论:对于无继承的类的成员函数,是独立出来的,类的内存中并没有存储相应的函数信息。对于成员函数的访问,是通过编译器完成的。
可是,在这里,我们少考虑了一种情况:虚函数的存在。这是一种特例,内存将会为其分配相应空间。在这里先不做讨论,且看下篇的具体分析。
#3楼 2015-07-23 16:23 melonstreet
对于博主结论“对于无继承的类的成员函数...”不是特别理解,在我看来,对于派生类,它的成员函数(包括从父类继承而来的)依然是只有一个副本。函数代码是存储在对象空间之外的。如果对同一个类定义了10个对象,这些对象的成员函数对应的是同一个函数代码段,而不是10个不同的函数代码段,而区别不同的函数行为,是依赖类对象的this指针来实现。
对于博主最后一句“虚函数的存在。这是一种特例,内存将会为其分配相应空间。” 在我看来,类是否定义了虚函数,或类是否有继承而来虚函数,类对象中只是多了个几个字节的虚表指针,类对象还是没有为虚函数分配内存。当然只是我个人的理解。希望和博主一起探讨。如果博主不服,来打我啊!!!
有道理!
可能我写得有问题!
问题一:正如您说的,对于派生类,它的成员函数(包括从父类继承而来的)只有一个副本。其实对于所有的类成员函数,它都仅有一个副本。我一开始猜想的只是类或对象中是否有成员函数的指针而已。
问题二:其实我单独把“没有继承的类”拿出来讲是有问题的,应该说“没有虚函数的类”。
感谢大神评论,博主不得不服,不敢得罪,改天就把它改好。至于为什么不现在改,因为懒。( ╯□╰ )。
http://www.cnblogs.com/QG-Hothoren/p/4663061.html
VC++中的类的内存分布(上)(通过强制转换,观察地址,以及地址里的值来判断)的更多相关文章
- VC++中的类的内存分布(上)
0.序 目前正在学习C++中,对于C++的类及其类的实现原理也挺感兴趣.于是打算通过观察类在内存中的分布更好地理解类的实现.因为其实类的分布是由编译器决定的,而本次试验使用的编译器为VS2015 RC ...
- C++ 类的内存分布
C++类内存分布 转自:http://www.cnblogs.com/jerry19880126/p/3616999.html 先写下总结,通过总结下面的例子,你就会明白总结了. 下面总结一下: ...
- VC中结构体的内存布局
看了 VC++中内存对齐 这篇文章,感觉说复杂了,根据我的总结,要算出结构体的内存大小和偏移量,只要清楚结构体各成员的内存布局就行了,下面介绍一下我总结的规则,有不对之处,欢迎回复. 1.实际PACK ...
- VC++中经常出现的内存泄露和指针问题
要养成良好的编程习惯,每次用new开辟的新空间马上先写好释放语句delete.指针在程序中往往有很多细节问题,比如1.指针作为返回值,某个分支中进行赋值返回,另一个分支却没有值.2.指针作为函数参数传 ...
- 【C++对象模型】使用gcc、clang和VC++显示C++类的内存布局
引言 各种C++实现对C++类/对象的内存布局可能有所不同,包括数据成员的顺序.虚函数表(virtual table: vtbl)的结构.继承关系的处理等.了解C++类/对象的布局,对于理解C++各种 ...
- Java对象在虚拟机中的创建、内存分布、访问定位以及死亡判定
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6535156.html 一:虚拟机中对象的创建 1:虚拟机遇到new指令时,在常量池检索是否有对应的符号引用, ...
- AndroidStudio中Handler类的内存溢出风险
package com.test.king.xmlparser; import android.annotation.SuppressLint; import android.app.Activity ...
- VC中CRect类的简单介绍
CRect CRect类与Windows RECT结构相似,并且还包括操作CRect对象和Windows RECT结构的成员函数.在传递LPRECT,LPCRECT或RECT结构作为参数的任何地方,都 ...
- zend framework获取数据库中枚举类enum的数据并将其转换成数组
在model中建立这种模型,在当中写入获取枚举类的方法 请勿盗版,转载请加上出处http://blog.csdn.net/yanlintao1 class Student extends Zend_D ...
随机推荐
- linux创建用户和组
linux下创建用户(一) Linux 系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统.用户的账号一方面可以帮助系 ...
- js call方法介绍
call 方法 请参阅 应用于:Function 对象 要求 版本 5.5 调用一个对象的一个方法,以另一个对象替换当前对象. call([thisObj[,arg1[, arg2[, [,.argN ...
- Python核心编程读笔 4
第五章 数字 二.整形 1 布尔型 2 标准整数类型 3 长整型 数字后面加L,能表示非常非常大的数字 目前,整形和长整型逐渐统一!!! 三.双精度浮点数 四.复数 有关复数的几个概念: 表示虚数的语 ...
- [C++]KMP算法实现
KMP算法说明:http://zh.wikipedia.org/wiki/%E5%85%8B%E5%8A%AA%E6%96%AF-%E8%8E%AB%E9%87%8C%E6%96%AF-%E6%99% ...
- (转)IE劫持原理 BHO
为什么“浏览器劫持”能够如此猖狂呢?放眼众多论坛的求助贴,我们不时可以看到诸如“我的IE被主页被改了,我用杀毒工具扫了一遍都没发现病毒,我把主页改回自己的地址,可是一重启它又回来了!”.“我的系统一开 ...
- [C#参考]Struct结构体
结构体是一种简单的用户自定义类型,也是类的一种轻量级的替代品. 相似之处:他们都有构造函数.属性.方法.字段.操作符.嵌套类型和索引器. 差异之处:类是一种引用类型,而结构体是一种值类型.因此结构体一 ...
- 管理node_modules
http://stackoverflow.com/questions/15225865/centralise-node-modules-in-project-with-subproject
- PHP EOF(heredoc)的使用
<?php /* Heredoc技术,在PHP手册和技术书籍中一般没有详细讲述,只是提到了这是一种Perl风格的字符串输出技术. 目前一些论坛程序和CMS系统使用了这种技术,前不久看一个朋友的P ...
- [原创]使用GCC创建 Windows NT 下的内核DLL
原文链接:使用GCC创建 Windows NT 下的内核DLL 在温习<<Windows 2000 Driving>>分层驱动程序一章的时候,看到了关于紧耦合驱动连接方式,这种 ...
- 运行预构建 Linux 映像的 Windows Azure 虚拟机中的交换空间 – 第 1 部分
本文章由 Azure CAT 团队的 Piyush Ranjan (MSFT) 撰写. 随着基础结构服务(虚拟机和虚拟网络)近期在 Windows Azure 上正式发布,越来越多的企业工作负荷正在向 ...