sizeof struct 问题

来源:互联网 发布:ubuntu软件包管理 编辑:程序博客网 时间:2024/05/17 22:20

sizeof()
  sizeof是运算符,可用于任何变量名、类型名或常量值,当用于变量名(不是数组名)或常量时,它不需要用圆括号。
  它在编译时起作用,而不是运行时。
  这是初学者问得最多的一个问题,所以这里有必要多费点笔墨。让我们先看一个结构体:
  struct S1
  {
  char c;
  int i;
  };
  问sizeof(s1)等于多少聪明的你开始思考了,char占1个字节,int占4个字节,那么加起来就应该是5。是这样吗你在你机器上试过了吗也许你是对的,但很可能你是错的!VC6中按默认设置得到的结果为8。
  Why为什么受伤的总是我
  请不要沮丧,我们来好好琢磨一下sizeof的定义——sizeof的结果等于对象或者类型所占的内存字节数,好吧,那就让我们来看看S1的内存分配情况:
  S1 s1 = { 'a', 0xFFFFFFFF };
  定义上面的变量后,加上断点,运行程序,观察s1所在的内存,你发现了什么
  以我的VC6.0为例,s1的地址为0x0012FF78,其数据内容如下:
  0012FF78: 61 CC CC CC FF FF FF FF
  发现了什么怎么中间夹杂了3个字节的CC看看MSDN上的说明:
  When applied to a structure type or variable, sizeof returns the actual size, which may include padding bytes inserted for alignment.
  原来如此,这就是传说中的字节对齐啊!一个重要的话题出现了。
  为什么需要字节对齐计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被 4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。
  让我们交换一下S1中char与int的位置:
  struct S2
  {
  int i;
  char c;
  };
  看看sizeof(S2)的结果为多少,怎么还是8再看看内存,原来成员c后面仍然有3个填充字节,这又是为什么啊别着急,下面总结规律。
  字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
  1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
  2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
  3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
  对于上面的准则,有几点需要说明:
  1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢?

      因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。
  结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:
  #define offsetof(s,m) (size_t)&(((s *)0)->m)
  例如,想要获得S2中c的偏移量,方法为
  size_t pos = offsetof(S2, c);// pos等于4
  2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。
  这里叙述起来有点拗口,思考起来也有点挠头,还是让我们看看例子吧(具体数值仍以VC6为例,以后不再说明):
  struct S3
  {
  char c1;
  S1 s;
  char c2;
  };
  S1的最宽简单成员的类型为int,S3在考虑最宽简单类型成员时是将S1“打散”看的,所以S3的最宽简单类型为int,这样,通过S3定义的变量,其存储空间首地址需要被4整除,整个sizeof(S3)的值也应该被4整除。
  c1的偏移量为0,s的偏移量呢这时s是一个整体,它作为结构体变量也满足前面三个准则,所以其大小为8,偏移量为4,c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13是不能被4整除的,这样末尾还得补上3个填充字节。最后得到 sizeof(S3)的值为16。
  通过上面的叙述,我们可以得到一个公式:
  结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:
  sizeof( struct ) = offsetof( last item ) + sizeof( last item ) + sizeof( trailing padding )
  到这里,朋友们应该对结构体的sizeof有了一个全新的认识,但不要高兴得太早,有一个影响 sizeof的重要参量还未被提及,那便是编译器的 pack指令。它是用来调整结构体对齐方式的,不同编译器名称和用法略有不同,VC6中通过#pragma pack实现,也可以直接修改/Zp编译开关。#pragma pack的基本用法为:#pragma pack( n ),n为字节对齐数,其取值为1、2、4、8、16,默认是8,如果这个值比结构体成员的sizeof值小,那么
  该成员的偏移量应该以此值为准,即是说,结构体成员的偏移量应该取二者的最小值,
  公式如下:
  offsetof( item ) = min( n, sizeof( item ) )
  再看示例:
  #pragma pack(push) // 将当前pack设置压栈保存
  #pragma pack(2) // 必须在结构体定义之前使用
  struct S1
  {
  char c;
  int i;
  };
  struct S3
  {
  char c1;
  S1 s;
  char c2;
  };
  #pragma pack(pop) // 恢复先前的pack设置
  计算sizeof(S1)时,min(2, sizeof(i))的值为2,所以i的偏移量为2,加上sizeof(i)等于6,能够被2整除,所以整个S1的大小为6。
  同样,对于sizeof(S3),s的偏移量为2,c2的偏移量为8,加上sizeof(c2)等于9,不能被2整除,添加一个填充字节,所以sizeof(S3)等于10。
  现在,朋友们可以轻松的出一口气了,:)
  还有一点要注意,“空结构体”(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。如下:
  struct S5 { };
  sizeof( S5 ); // 结果为1

#include <iostream>using namespace std;typedef struct _A{char c;int i;} A;typedef struct _B{A a;double d;} B;typedef struct _C{A a;char c;} C;int main(void){cout << sizeof(A) << endl;cout << sizeof(B) << endl;cout << sizeof(C) << endl;return 0;}8 16  12
struct A  {      A();      ~A();      int m1;      int m2;  }a;  struct B  {      B();      ~B();      int m1      char m2;      static char m3;  }b;  struct C  {      C();      virtual ~C();      int m1;      int m2;  }c;  

sizeof(a)=8 sizeof(b)=8 sizeof(c)=12
拥有virtual函数的类中,需要维护一个虚函数表的指针,一个指针的大小是4字节
static不占用空间
C++中类到底占多少空间以及内存字节对齐的原理

#include<iostream>using namespace std;int main(){class S{};cout<<sizeof(S);return 0;}

程序居然输出为1!到底是怎么回事?很令人纠结,下面便是我找到的两篇文章,很清楚的解释了这些问题,人家已经表述的很好了,我也就不再罗嗦,直接搬过来了
首先我在这里要声明一点——类在未初始化之前确实不会分配空间,这里探讨的是sizeof(类)的问题,详细情况看下面的例子

int main(){    class S{};    S sb;    cout<<sizeof(S)<<endl;    //if(&S)    //cout<<"S地址为:"<<&S;    if(&sb)    cout<<sizeof(sb)<<endl<<"sb地址为:"<<&sb;    system("pause");} 去掉注释部分,代码就是错误的!可见,确实没有分配内存!如有什么问题,欢迎交流!

C++中的类所占内存空间总结
类所占内存的大小是由成员变量(静态变量除外)决定的,成员函数(这是笼统的说,后面会细说)是不计算在内的。
摘抄部分:
成员函数还是以一般的函数一样的存在。a.fun()是通过fun(a.this)来调用的。所谓成员函数只是在名义上是类里的。其实成员函数的大小不在类的对象里面,同一个类的多个对象共享函数代码。而我们访问类的成员函数是通过类里面的一个指针实现,而这个指针指向的是一个table,table里面记录的各个成员函数的地址(当然不同的编译可能略有不同的实现)。所以我们访问成员函数是间接获得地址的。所以这样也就增加了一定的时间开销,这也就是为什么我们提倡把一些简短的,调用频率高的函数声明为inline形式(内联函数)。
(一)
class CBase
{
};
sizeof(CBase)=1;
为什么空的什么都没有是1呢?
c++要求每个实例在内存中都有独一无二的地址。//注意这句话!!!!!!!!!!    
空类也会被实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
(二)
class CBase
{
 int a;
 char p;
};
sizeof(CBase)=8;
记得对齐的问题。int 占4字节//注意这点和struct的对齐原则很像!!!!!        
char占一字节,补齐3字节
(三)
class CBase         
{
public:
 CBase(void);
 virtual ~CBase(void);
 private:
 int   a;
 char *p;
};
再运行:sizeof(CBase)=12
C++ 类中有虚函数的时候有一个指向虚函数的指针(vptr),在32位系统分配指针大小为4字节。无论多少个虚函数,只有这一个指针,4字节。//注意一般的函数是没有这个指针的,而且也不占类的内存。
(四)
class CChild : public CBase             
{
public:
 CChild(void);
 ~CChild(void);
 virtual void test();
 private:
 int b;
};
输出:sizeof(CChild)=16;
可见子类的大小是本身成员变量的大小加上父类的大小。//其中有一部分是虚拟函数表的原因,一定要知道父类子类共享一个虚函数指针
(五)

#include<iostream.h>class a {};class b{};class c:public a{ virtual void fun()=0;};class d:public b,public c{};int main(){ cout<<"sizeof(a)"<<sizeof(a)<<endl; cout<<"sizeof(b)"<<sizeof(b)<<endl; cout<<"sizeof(c)"<<sizeof(c)<<endl; cout<<"sizeof(d)"<<sizeof(d)<<endl; return 0;}

序执行的输出结果为:
sizeof(a) =1
sizeof(b)=1
sizeof(c)=4
sizeof(d)=8
前三种情况比较常见,注意第四种情况。类d的大小更让初学者疑惑吧,类d是由类b,c派生迩来的,它的大小应该为二者之和5,为什么却是8 呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则,里哪个最近的倍数,就是该类的大小,所以类d的大小为8个字节.
总结:
空的类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。
(一)类内部的成员变量:
普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。   
static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。   
(二)类内部的成员函数:
普通函数:不占用内存。   
虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的。   
原文地址
http://blog.renren.com/share/230050020/8908645765
参考资料
深度探索C++对象模型

 

原创粉丝点击