网站已经改版为Wordpress版本,这里是旧版本的快照,请不要在页面中留言.

浅谈C++结构体和类,对象的大小该如何计算?《一》

    本文出自悠然品鉴,转载请注明出处:http://www.youranshare.com/blog/sid/90.html

   文章列表:

       · 浅谈C++结构体和类,对象的大小该如何计算?《一》

       · 浅谈C++结构体和类,对象的大小该如何计算?《二》结构体对齐

       · 浅谈C++结构体和类,对象的大小该如何计算?《三》结构体内部含有数组和结构体等


    在C++中,结构体和类他们都是有构造函数、析构函数和成员函数的,他们两者的根本区别就是:结构体中访问控制默认是public的,而类中默认的访问控制是private的。对于C++中的结构体而言,public、protected、private的访问都是在编译期进行检查的,当越权访问的时候,编译过程中会给出此类的错误并给与提示,在编译成功后,程序在执行的过程中不会有任何的检查和限制,这一点你可以通过类的指针偏移做一下测试。因此在反汇编中,C++中的结构体与类没有分别,两者的原理是相同的,只是类型名称不同。

说一下对象内存的布局

对于类和对象的关系想必你已经很了解了,类是一个抽象的概念,对象是一个实例化的具体存在的。这里我们以一个简单的类,谈一下类与对象的关系:

#include <iostream>
using namespace std;
 
class CNumber{
public:
      CNumber(){
           m_One = 1;
           m_Two = 2;
      }
      int GetNumberOne(){
           return m_One;
      }
      int GetNumberTwo(){
           return m_Two;
      }
private:
      int m_One;
      int m_Two;
 
};
int main()
{
      CNumber num;
      cout<<num.GetNumberOne()<<endl;
 
      //尝试着用指针访问
      cout<<*(int*)(void*)&num<<endl;
      cout<<*((int*)(void*)&num + 1)<<endl;
      return 0;
}


我们知道类的私有成员是不能被直接访问到的,但是我们在程序运行后通过指针的偏移还是依然可以读取内存的数据,就像结构体一样,如图所示我们的运行结果:

                                             

可以发现,将对象的指针强制转换成我们需要的数据类型,然后通过指针在内存中的偏移可以访问到对象的私有成员!这也就说明了C++中类保护属性是编译的时候进行检测的!

C++中类的成员变量和结构体一样,也是按照顺序依次放到内存中的,先定义的数据成员放到地地址,后定义的数据成员放到高地址处,但是对象的大小只包含数据成员,类的成员函数属于执行代码,不属于类对象的数据。

你可以通过sizeof获取到CNumber对象的大小,他们的大小都是8Byte,这8Byte字节是由类中的两个数据成员主城,他们都是int类型,格子的长度都为4Byte。从内存的布局上来看,类与一个数组非常相似,都是由多个数据成员组成,但是类的能力要远远大过数组。类的成员变量类型定义非常广泛,除了本身对象之外,任何已知的数据类型都可以在内种作为成员变量进行定义。

为什么在类中不能定义自身的对象呢?因为类需要在申请内存的过程中计算出自身的实际大小,用于实例化对象。如果你在类中定义了自身的对象变量,那么在计算类中各个数据成员长度时,又会因为回到自身导致无限递归下去,而这个递归没有出口,所以不能在类中定义自身的对象成员变量。但是指针是可以的,因为指针的长度是确定的,也就是相当于一个常量值,因此定义自身的指针是不会影响到类本身大小的计算的。根据上面所说的东西,我们是否可以定义一个公式用于描述类的对象长度呢?也许你会认为下面的公式可以用于计算对象的长度:

对象长度= sizeof(成员1) + …+szieof(成员2)+…+sizeof(成员n)

这里我明确的告诉你,这个公式是错误的,对象大小的计算远远没有那么简单,即是我们抛开虚函数和继承的原因,仍然有3中情况能够推翻此公式:

   空类

   内存对齐问题

   静态数据成员

当出现上面的情况时,类的对象长度的计算就需要小心了:

空类: 就是说类内部没有任何数据成员,但是实际上类对象的长度可是为1字节

内存对齐: 在VC++6.0中,类和结构体中的数据成员是根据他们在类或者结构体中出现的顺序来依次申请内存的,但是由于内存对齐的原因,他们并不会一定像数组那样内存是连续排列的,由于数据类型不同,因此占用的内存空间大小也会不同,在申请内存的时候会按照一定的规则.

 

我们以一个结构体为例子来看看这个结构体中成员变量地址的排列:

struct TagTest{
          char a;
          int b;
     };


      下一段显示地址的片段,来看看 成员变量 a b的地址

       如图所示,可以看到a b的地址相差为4字节,这也就是说a b这两个变量是没有连续的分配在内存中的,这中现象就是内存地址对齐导致的.


        在为结构体和类中的数据成员分配内存的时候,结构体中的当前数据成员类型长度为M,指定对齐值为N(编译器指定的,例如为8Byte),那么实际上的对齐值q = Min(M,N),也就是说这个数据成员所在的地址必须为q的倍数,所以在上面的结构体中,数据成员b,他的对齐值为q=Min(4,8) 也就是4,所以b的地址必须为4的倍数,虽然前面的数据成员的地址a为18086720,b只是占用了1个字节,但是b的地址需要为4的倍数,那么就需要在数据成员a后面偏移3个字节的位置放置b,如图所示的a和b的内存分布:

                                               

QQ图片20141014131450.png

      下面我们来分析一下ab的地址分配过程:

   开始分配a的地址,a为一个char类型,他的对齐值M=1,在VC++6.0中编译器的默认对齐值为8,那么q = Min(1,8),也就是q=1,地址需要是1的倍数,所以直接分配地址就行了。例如分配了地址为18086720(十进制)

   开始分配b的地址,b是一个int类型对齐值为4Byte,那么q = Min(4,8),也就是q = 4,他所在的地址需要是4的倍数,如果此时我们直接将b分配在a的后面,那么b的地址将会是18086721,不满足对齐的地址,所以需要往后偏移3个字节,对应的地址就是18086724,这个地址满足对齐值4,所以系统给b分配的地址为18086724,分配完毕。

通过上面的分配我们可以看到,这个结构体一共占用了8Byte,而不是我们认为的5字节。另外值得说的是a后面的那3个字节里面可不是填充的0x00,一般系统都会填充0xCC。

 

看完上面的东西,有些童鞋肯定感觉到了一个问题:“结构体本身也是一种数据类型,既然是数据类型,就像int一样,结构体本身也是有对齐的吧“.

答案是肯定的,数据类型都是有对齐值的,数据结构本身也是一中类型,与int基本类型无差别,当然也存在对齐值的问题,下面我给出一个数据结构:

struct STest{
    double da;//8字节大小
    int    ib;//4字节大小
    short  sc;//2字节大小
};

这个数据结构STest的大小是16而不是14,为什么?这个问题我将在下一篇博文中给出介绍.



  • 标签:
  • C++ 结构体
  • C++类
  • C++内存结构
网站已经改版为Wordpress版本,这里是旧版本的快照,请不要在页面中留言.