总结sizeof的用法(包括位段)

**

一、先来热身

**

编译环境是vs2013(32位)

#include<iostream>  
#include<stdio.h>  
#include<string.h>  
using namespace std;  
typedef struct  
{  
    int a;  
    char b;  
}A_t;  
typedef struct  
{  
    int a;  
    char b;  
    char c;  
}B_t;  
typedef struct  
{  
    char a;  
    int b;  
    char c;  
}C_t;  
void main()  
{  
    char*a=0;  
    cout<<sizeof(a)<<endl;//4  
    cout<<sizeof(*a)<<endl;//1
    cout<<sizeof(A_t)<<endl;//8  
    cout<<sizeof(<B_t)<<endl;//8  
    cout<<sizeof(C_t)<<endl;//12  
}

二、语法

sizeof有三种语法形式,如下:
1) sizeof( object ); // sizeof( 对象 );
2) sizeof( type_name ); // sizeof( 类型 );
3) sizeof object; // sizeof 对象;

三、指针变量的sizeof

既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(以字节为单位),在64位系统中指针变量的sizeof结果为8。

#include<iostream>  
#include<stdio.h>  
#include<string.h>  
using namespace std;  
int main()  
{  
char *a=0;  
char* pc = "abc";   
int* pi;   
string* ps;   
char** ppc = &pc;   
<span style="color:#ff0000;">void (*pf)();// 函数指针</span>  
cout<<sizeof(char)<<endl; //1  
cout<<sizeof(a)<<endl;//4  
cout<<sizeof(*a)<<endl;//1  
cout<<sizeof(pc)<<endl; //4(指针)  
cout<<sizeof(pi)<<endl;//4(指针)  
cout<<sizeof(ps<)<<endl; //4(string型指针)  
cout<<sizeof(ppc<)<<endl; //4(指针)  
cout<<sizeof(pf)<<endl;//4  
}

指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存
大小相等,所以MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)。

四、数组的sizeof

数组的sizeof值等于数组所占用的内存字节数,如:

#include<iostream>  
#include<stdio.h>  
#include<string.h>  
using namespace std;  
int main()  
{  
char b1[]="123";  
int b2[3];  
int c1=sizeof(b1)/sizeof(char);  
int c2=sizeof(b1)/sizeof(b1[0]);  
int c3=sizeof(b2)/sizeof(int);
     int c4=sizeof(b2)/sizeof(b2[0]);  
cout<<sizeof(b1)<<' '<<c1<<' '<<c2<<endl;//4 4 4  
cout<<sizeof(b2)<<' '<<c3<<' '<<c4<<endl;//12(3*4 依赖int) 3 3  
}

1.数组长度

char a1[] = “abc”;
int a2[3];
sizeof( a1 ); // 结果为4,字符串末尾还存在一个NULL终止符
sizeof( a2 ); // 结果为3*4=12(依赖于int)

2.数组元素个数

int c1 = sizeof( a1 ) / sizeof( char ); // 总长度/单个元素的长度
int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 总长度/第一个元素的长度

3.数组“传址”(数组为函数参数)

我们可以思考一下,下面的c3,c4值应该是多少呢?

void foo3(char a3[3])   
{   
int c3 = sizeof( a3 ); // c3  ==?
}   
void foo4(char a4[])   
{   
int c4 = sizeof( a4 ); // c4 == ?
} 

这里函数参数a3已不再是数组类型,而是蜕变成指针,相当于char* a3,为什么?仔细想想就不难明白,我们调用函数foo1时,程序会在栈上分配一个大小为3的数组吗?不会!数组是“传址”的,调用者只需将实参的地址传递过去,所以a3自然为指针类型(char*),c3的值也就为4。

五. 结构体的sizeof

结构体相对而言最容易碰到而且最容易出错。让我们先看一个结构体:

struct S1
{
char c;
int i;
};

编译得到结果为8!
我们来好好琢磨一下sizeof的定义——sizeof的结果等于对象或者类型所占的内存字节数,好吧,那就让我们来看看S1的内存分配情况:
S1 s1 = { a , 0xFFFFFFFF };
定义上面的变量后,加上断点,运行程序,观察s1所在的内存,你发现了什么?
以我的Vs为例,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.
原来如此,这就是传说中的字节对齐啊!
1.怎么判断内存对齐规则,sizeof的结果怎么来的,牢记如下3条规则(在没有#pragma pack宏的情况下):
(1)数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
(2)结构体作为成员:如果一个结构体里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(struct a 里存有struct b,b里有char,int,double等元素,那么b应该从8的整数倍开始存储)。
(3)收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
类型
对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)
Char
偏移量必须为sizeof(char)即1的倍数
int
偏移量必须为sizeof(int)即4的倍数
float
偏移量必须为sizeof(float)即4的倍数
double
偏移量必须为sizeof(double)即8的倍数
Short
偏移量必须为sizeof(short)即2的倍数

#include<iostream>  
#include<stdio.h>  
#include<string.h>  
using namespace std;  
typedef struct bb  
{  
 int id;             //[0]....[3]  
 double weight;      //[8].....[15]      原则1  
 float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3  
}BB;  
typedef struct aa  
{  
char name[2];  //[0] [1]  
int id;     //[4]....[7]   原则1  
double score;// [8]...[15]   
short t;    //[16]...[17]  
BB b;   //[24]...[47]  原则23  
}AA;  
int main()  
{  
cout<<sizeof(BB)<<endl; //为24  
cout<<sizeof(AA)<<endl; //为48  
return 0;  
} 

2.带#pragma pack()
在代码前加上一句#pragma pack(1),
设置结构体的边界对齐为1个字节,也就是所有数据在内存中是连续存储的。让编译器将结构体数据强制连续排列,这样的话,
bb就是4+8+4=16。Aa就是2+4+8+2+16=32.

六、联合体的sizeof

结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是复合类型,这里,复合类型成员是被作为整体考虑的。
所以,下面例子中,U的sizeof值等于sizeof(s)。

union U   
{   
int i;   
char c;   
AA s;   
}; 

七、类的sizeof

1、空类的sizeof是1。空类是指没有成员的类,类中的函数不占空间,除非是虚函数。

 class A  
        {  
           public:  
                  A(){}  
                  ~A(){}  
                  void fun(){}  
         }; 

sizeof(A)是1.
注:

 class A1  
        {  
             public:  
                      A1(){}  
                     ~A1(){}  
                     void fun(){}  
                      char a[0];  
         };

sizeof(A1)也是1.(VC6.0下编译)

2、若类中包含成员,则类对象的大小只包括其中非静态成员经过对齐所占的空间,对齐方式和结构体相同。如:

class A
{
public:
  int b;
  float c;
  char d;
};

sizeof(A)是12.

class A  
{  
public:  
  static int a;  
  int b;  
  float c;  
  char d;  
};

sizeof(A)是12.

3、若类中包含虚函数,则无论有几个虚函数,sizeof类都等于sizeof(数据成员)的和+sizeof(V表指针,为4),

说明:有虚函数的类有个virtual table(虚函数表),里面包含了类的所有虚函数,类中有个virtual table pointers,通常成为vptr指向这个virtual table,占用4个字节的大小。

class Base  
{  
      public:  
             Base(){cout<<"Base-ctor"<<endl;}  
             ~Base(){cout<<"Base-dtor"<<endl;}  
             int a;  
             virtual void f(int) {cout<<"Base::f(int)"<<endl;}  
             virtual void f(double){cout<<"Base::f(double)"<<endl;}//共有两个虚拟函数(virtual)  
};

sizeof(Base)为8.

4、对于子类,它的sizeof是它父类成员(无论成员是public或private),再加上它自己的成员,对齐后的sizeof,如:

class A2  
{  
      public:  
             int a;  
      private:  
              char b;  
};  

class A3:public A2  
{  
      public:  
             char b;  
             short a;        
 }

sizeof(A3)是12.

5、对于子类和父类中都有虚函数的情况,子类的sizeof是它父类成员(无论成员是public或private),再加上它自己的成员,对齐后的sizeof,再加4(虚表指针)。如:

class Base  
{  
      public:  
             Base(){cout<<"Base-ctor"<<endl;}  
             ~Base(){cout<<"Base-dtor"<<endl;}  
             int a;  
             virtual void f(int) {cout<<"Base::f(int)"<<endl;}  
             virtual void f(double){cout<<"Base::f(double)"<<endl;}  
};  

class Derived:public Base  
{  
  public:  
         Derived(){cout<<"Derived-ctor"<<endl;}  
         int b;  
         virtual void g(int){cout<<"Derived::g(int)"<<endl;}  
};

sizeof(Derived)是12.

6、对于虚继承的子类,其sizeof的值是其父类成员,加上它自己的成员,以及它自己一个指向父类的指针(大小为4),对齐后的sizeof。如:

#include   <iostream.h>      
  class   a     
  {     
  private:     
  int   x;     
  };     

  class   b:   virtual   public   a     
  {     
  private:     
  int   y;     
  };       
  class   c:   virtual   public   a     
  {     
  private:     
  int   z;     
  };     
  class d:public   b,public   c     
  {     
  private:     
  int   m;     
  };     
  int main(int argc,char* argv[])     
  {     
  cout<<sizeof(a)<<endl;     
  cout<<sizeof(b)<<endl;     
  cout<<sizeof(c)<<endl;     
  cout<<sizeof(d)<<endl;     
  return   0;     
  } 

在VC6.0下调试结果为

  4   
  12   
  12   
  24

sizeof(b)和sizeof(c)相同,都是4+4+4=12。
sizeof(d)是sizeof(b)(为12)+sizeof(c)(为12)-b和c相同的部分(a的成员,大小是4)+d自己的成员(大小为4)=24

7、对于既有虚继承又有虚函数的子类,其sizeof的值是其父类成员(计算虚表指针大小+4),加上它自己的成员(计算虚表指针大小+4),以及它自己一个指向父类的指针(大小为4),对齐后的sizeof。

class Base  
{  
public:  
 Base(){cout<<"Base-ctor"<<endl;}  
 ~Base(){cout<<"Base-dtor"<<endl;}  
 virtual void f(int) {cout<<"Base::f(int)"<<endl;}  
virtual void f(double){cout<<"Base::f(double)"<<endl;}  
};  

class Derived:virtual public Base  
{  
public:  
 Derived(){cout<<"Derived-ctor"<<endl;}  
 virtual void g(int){cout<<"Derived::g(int)"<<endl;}  
};

sizeof(Base)=4
sizeof(Derived)=12 (父类虚表指针大小4+自己虚表指针大小4+子类指向父类的一个指针大小4=12)

七、C结构体之位域(位段)的sizeof
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
(一)位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:

struct 位域结构名
{

位域列表

};
其中位域列表的形式为:

类型说明符 位域名:位域长度

位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:

 struct bs  
{  
  int a:8;  
  int b:2;  
  int c:6;  
}data; 

说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:

  1. 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
 struct bs  
{  
    unsigned a:4  
    unsigned b:5 /*从下一单元开始存放*/  
    unsigned c:4  
}
  1. 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度。

  2. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:

struct k  
{  
    int a:1  
    int :2 /*无位域名,该2位不能使用*/  
    int b:3  
    int c:2  
}; 

(二)位域

#include<iostream>
 using namespace std;  
 struct A  
 {  
     int a:5;  
    int b:3;  
 };  
 int main(void)  
{  
    char str[100] = "0134324324afsadfsdlfjlsdjfl";  
        struct A d;  
    memcpy(&d, str, sizeof(A));  
    cout << d.a << endl;  
    cout << d.b << endl;  
    return 0;  
}

代码在32位x86机器上输出:

$ ./langxun.exe
-16
1

解析:在默认情况下,为了方便对结构体内元素的访问和管理,当结构体内的元素长度都小于处理器的位数的时候,便以结构体里面最长的元素为对其单位,即结构体的长度一定是最长的数据元素的整数倍;如果有结构体内存长度大于处理器位数的元素,那么就以处理器的位数为对齐单元。由于是32位处理器,而且结构体中a和b元素类型均为int(也是4个字节),所以结构体的A占用内存为4个字节。
上例程序中定义了位域结构A,两个个位域为a(占用5位),b(占用3位),所以a和b总共占用了结构A一个字节(低位的一个字节)。
当程序运行到14行时,d内存分配情况:

高位 00110100 00110011 00110001 00110000 低位(ASCII码)
‘4’ ‘3’ ‘1’ ‘0’
其中d.a和d.b占用d低位一个字节(00110000),d.a : 10000, d.b : 001
d.a内存中二进制表示为10000,由于d.a为有符号的整型变量,输出时要对符号位进行扩展,所以结果为-16(二进制为11111111111111111111111111110000)
d.b内存中二进制表示为001,由于d.b为有符号的整型变量,输出时要对符号位进行扩展,所以结果为1(二进制为00000000000000000000000000000001)

(三)位域的对齐

  如果结构体中含有位域(bit-field),那么VC中准则是:

  1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
  2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
  3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式;

  系统会先为结构体成员按照对齐方式分配空间和填塞(padding),然后对变量进行位域操作。

猜你喜欢

转载自blog.csdn.net/snow_rain_1314/article/details/78467325