0.序

  目前正在学习C++中,对于C++的类及其类的实现原理也挺感兴趣。于是打算通过观察类在内存中的分布更好地理解类的实现。因为其实类的分布是由编译器决定的,而本次试验使用的编译器为VS2015 RC,其编译环境为VC++,这里感谢@shenzhigang 提醒。所以此处的标题为《VC++中的类的内存分布》。因为博主可能比较懒,所以把这个知识点分作两次写。( ╯□╰ )。

1.对无虚函数类的探索

  1.1 空类

  我们先一步一步慢慢来,从一个空的类开始。

  1. //空类
  2. class test
  3. {
  4. };
  1. int main(int argc, char *argv[])
  2. {
  3. test ts;
  4. cout << sizeof(ts) << endl;
  5. return ;
  6. }

  结果输出的是1。

  于是我们推测,对于一个空类,内存总会为其分配一个字节的空间。为此,我们可以来验证一下:

 

  1. int main(int argc, char *argv[])
  2. {
  3. test ts;
  4. char ch = '';
  5. int a1, a2;
  6. a1 = (int)(&ts);
  7. a2 = (int)(&ts + );
  8. memcpy(&ts, &ch, );
  9. cout << sizeof(ts) << endl;
  10. return ;
  11. }

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

  结果真的把ch里面的内容写入到ts中了。

  综上可以得出一个结论,对于一个空类,编译器总会为其分配一个字节的内存。

  1.2 仅含数据成员的类

  首先对于数据成员为一个字节(char)的类,通过上述的测试代码,结果和空类一样,编译器分配了一个字节的空间给了类中的char。这是在我们的预料之内的事。

  可是当我们的类设计成含有不同类型的数据结构的时候,结果就不同了:

  1. class test
  2. {
  3. public:
  4. char c;
  5. int i;
  6. };

  程序的输出结果是。可以看到,此时类占用内存的空间是8个字节。

  这就涉及到“内存对齐”了。所以接下来我们就先来探讨一下C++里的“内存对齐”。

内存对齐:

  对于一个类(结构体),编译器为了提高内存读取速率以及可移植性,存在一种称作为“内存对齐”的规则。一般对于内存对齐,编译器会帮你完成,但是这种工作其实是可以由编程者自己完成的。

  C++中,可以使用#pragma pack(n)的预编译处理进行设置“对齐系数”。(这个对齐系数在VC中一般默认为8。)

  为了能够更好地了解内存中内存对齐的流程,我特地画了分配空间的流程图。(仅本人自己理解,如有谬误请各位大侠指出。)

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

  1. class test
  2. {
  3. public:
  4. char c;
  5. int i;
  6. short s;
  7. };

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

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

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

  可以看到,不同的对齐系数会使内存的分布呈现不同的格局。

  讨论完内存对齐之后,我们来看一看类中的static成员。

类中的static成员:

  我们设计一个这样的类,类中包括有静态数据成员。

  1. class test
  2. {
  3. public:
    static int si;
  4. char c;
  5. int i;
  6. short s;
  7. };

  结果我们发现,该类的大小还是和之前无异。

  同时,我们通过cout语句查看类中的static成员地址。

  1. cout << sizeof(ts) << '\n' << (int)&ts.si << '\n' << (int)(&ts) << endl;

  

  结果得出的类的地址和类的static成员的地址相差十万八千里。显而易见,类中的static成员并不是和类储存在一起的。

  综上可以得到的结论是:类中的成员数据中,仅有非static成员数据才会为其开辟内存空间。

  1.3 包含成员函数的类

  这里要先感谢一下@melonstreet 提到的问题,已改正。

  对于类中的成员函数,我们知道,对于所有类,每个成员函数都只有一个副本。函数代码是存储在对象空间之外的。如果对同一个类定义了10个对象,这些对象的成员函数对应的是同一个函数代码段,而不是10个不同的函数代码段。在这里,关于具体到对象的成员函数是如何调用的,我们有两种猜想:第一种猜想是每一个对象中都必须开辟一段内存,用来存储指向类中的成员函数的指针,每次当外部对对象的成员函数进行调用的时候,通过访问对象空间中的函数指针从而访问函数。第二种猜想是类中的成员函数是独立出来的,每个对象中并没有储存成员函数的相关信息,而成员函数的调用是通过编译器在编译的时候自动帮我们选择要访问的函数。为此,我们也要进行一些测试。

  1. class test
  2. {
  3. public:
  4. static int si;
  5. char c;
  6. int i;
  7. short s;
  8. test():c(''),i(),s() {};
  9. void print(void) { cout << sizeof(test) << '\n' << (int)&si << '\n' << (int)this << endl; }
  10. };

  

  输出结果显示内存仍然不变,说明成员函数在类的内存中并不占空间。

  综上可以得出结论:对于无继承的类的成员函数,是独立出来的,类的内存中并没有存储相应的函数信息。对于成员函数的访问,是通过编译器完成的。

可是,在这里,我们少考虑了一种情况:虚函数的存在。这是一种特例,内存将会为其分配相应空间。在这里先不做讨论,且看下篇的具体分析。

VC++中的类的内存分布(上)的更多相关文章

  1. VC++中的类的内存分布(上)(通过强制转换,观察地址,以及地址里的值来判断)

    0.序 目前正在学习C++中,对于C++的类及其类的实现原理也挺感兴趣.于是打算通过观察类在内存中的分布更好地理解类的实现.因为其实类的分布是由编译器决定的,而本次试验使用的编译器为VS2015 RC ...

  2. C++ 类的内存分布

    C++类内存分布 转自:http://www.cnblogs.com/jerry19880126/p/3616999.html   先写下总结,通过总结下面的例子,你就会明白总结了. 下面总结一下: ...

  3. VC中结构体的内存布局

    看了 VC++中内存对齐 这篇文章,感觉说复杂了,根据我的总结,要算出结构体的内存大小和偏移量,只要清楚结构体各成员的内存布局就行了,下面介绍一下我总结的规则,有不对之处,欢迎回复. 1.实际PACK ...

  4. 【C++对象模型】使用gcc、clang和VC++显示C++类的内存布局

    引言 各种C++实现对C++类/对象的内存布局可能有所不同,包括数据成员的顺序.虚函数表(virtual table: vtbl)的结构.继承关系的处理等.了解C++类/对象的布局,对于理解C++各种 ...

  5. Java对象在虚拟机中的创建、内存分布、访问定位以及死亡判定

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6535156.html  一:虚拟机中对象的创建 1:虚拟机遇到new指令时,在常量池检索是否有对应的符号引用, ...

  6. VC++中经常出现的内存泄露和指针问题

    要养成良好的编程习惯,每次用new开辟的新空间马上先写好释放语句delete.指针在程序中往往有很多细节问题,比如1.指针作为返回值,某个分支中进行赋值返回,另一个分支却没有值.2.指针作为函数参数传 ...

  7. AndroidStudio中Handler类的内存溢出风险

    package com.test.king.xmlparser; import android.annotation.SuppressLint; import android.app.Activity ...

  8. VC中CRect类的简单介绍

    CRect CRect类与Windows RECT结构相似,并且还包括操作CRect对象和Windows RECT结构的成员函数.在传递LPRECT,LPCRECT或RECT结构作为参数的任何地方,都 ...

  9. C++类内存分布

    http://www.cnblogs.com/jerry19880126/p/3616999.html#undefined 书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看 ...

随机推荐

  1. Thinking In Web [原创作品]

    (转载请注明:http://zhutty.cnblogs.com, 交流请加群:164858883) 可能在大部分人来讲,前端就是可见的页面数据呈现正确就行.然而这样是不正确的,页面呈现是一部分,更多 ...

  2. 【Python基础】计算项目代码行数

    统计代码行数 # coding: utf-8 import os import sys import time def get_line_count(file_path): ""& ...

  3. 数据持久化之CoreData

    再次回归博客园, 已经实属不易了, 面临这近期忙忙碌碌的项目开发, 虽然并不是完全的没有闲暇时间, 但是怎么说呢, 也有着各种的无奈与曲折, 面临这产品需求的不断变化和页面的不断更新, 对于一个程序员 ...

  4. 【iOS开发-从网络上获取图片尺寸】

    实际开发过程中,容易碰到从网络上获取图片尺寸的场景,比如一个UIImageView要装载从网络上获取的图片,但要先设置其frame,此时又不知道图片尺寸,就要从网络上获取尺寸了.为了最好的用户体验,一 ...

  5. git开源项目协作

    开源项目协作 fork开源项目,即打开开源项目的github,然后点击fork按钮 pull request

  6. JQuery中常用的 属性选择器

    jQuery中使用$()作为选择符极大提高工作效率以及方便快捷;一些常用属性的选择器由以下几种 1) $('#id') id选择器 2) $('.class') css选择器,class类名 3) $ ...

  7. (转)VS无法启动调试:“生成下面的模块时,启用了优化或没有调试信息“

    中调试项目遇到错误提示,Visual Studio 2010(或VS2008或VS2005)启动调试的时候,弹出提示信息: 生成下面的模块时,启用了优化或没有调试信息: C:\WINDOWS\Micr ...

  8. 来自GitHub的Android UI开源项目

    最近在搞Android开发,做了一个项目后感觉,Android开发入门很是简单,但要能做出用户体验比较完美的APP实在是一件很不容易的事情!要达到一定的水准,估计还需要慢慢的积累,这里先保存一个Git ...

  9. PullToRefresh的使用

    主界面↓ package com.wangzhen.pulltorefresh; import java.util.ArrayList; import java.util.List; import c ...

  10. 一个基础的CURL类

    /** * 一个基础的CURL类 * * @author Smala */ class curl{ public $ch; public $cookie = '/cookie'; public $rs ...