sizeof用法总结

1,基本数据类型的sizeof

cout<<sizeof(char)<<endl;                     结果是1
cout<<sizeof(int)<<endl;                        结果是4
cout<<sizeof(unsigned int)<<endl; 结果是4 
cout<<sizeof(long int)<<endl;                结果是4
cout<<sizeof(short int)<<endl;               结果是2
cout<<sizeof(float)<<endl;                     结果是4
cout<<sizeof(double)<<endl;                 结果是8

2,指针变量的sizeof

char *pc ="abc";
sizeof( pc ); // 结果为4
sizeof(*pc);     // 结果为1
int *pi;
sizeof( pi ); //结果为4
sizeof(*pi);     //结果为4
char **ppc = &pc;  
sizeof( ppc ); // 结果为4   
sizeof( *ppc ); // 结果为4     
sizeof( **ppc ); // 结果为1
void (*pf)();// 函数指针
sizeof( pf );// 结果为4

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

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

写到这里,提一问,下面的c3,c4值应该是多少呢

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

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

4,结构体的sizeof

struct MyStruct
{
           double dda1;
           char dda;
           int type
};

//结果为16,为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。

5,含位域结构体的sizeof
示例1:

struct BF1
{
char f1 : 3;
char f2 : 4;
char f3 : 5;
};

其内存布局为:
|f1_|f2||f3||
|
|
|||||||||||||||
0 3 7 8 1316
位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(BF1)的结果为2。

6,含有联合体的结构体的sizeof

struct s1
{
           char *ptr,ch;                   //有指针变成4+4
           union A            //后面跟了A定义了一个类型,不占内存,而后面不跟A,是声明了结构体的一个成员,占内存,
          {
              short a,b;
              unsigned int c:2, d:1;
           };
           struct s1* next;                //指针占4
};//这样是8+4=12个字节
struct s1
{
           char *ptr,ch;                          
           union                                            //联合体是结构体的成员,占内存,并且最大类型是unsigned int,占4
          {
               short a,b;
               unsigned int c:2, d:1;
           };
           struct s1* next;                        
};//这样是8+4+4=16个字节

7,结构体体含有结构体的sizeof

struct S1
    {
     char c;
     int i;
    };
struct S3
    {
     char c1;
     S1 s;
     char c2;
    };
cout<<sizeof(S3);      //S3=16

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。\

8,带有#pragma pack的sizeof

它是用来调整结构体对齐方式的,不同编译器名称和用法略有不同,VC6中通过#pragma pack实现,也可以直接修改/Zp编译开关。#pragma pack的基本用法为:#pragma pack( n ),n为字节对齐
数,其取值为1、2、4、8、16,默认是8,如果这个值比结构体成员的sizeof值小,那么该成员的偏移量应该以此值为准,即是说,结构体成员的偏移量应该取二者的最小值,

再看示例:

#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。

另外,还有如下的一种方式:
_attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
attribute
((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

9,空结构体的sizeof

struct S5 { };
sizeof( S5 ); // 结果为1

10,类的sizeof
类的sizeof值等于类中成员变量所占用的内存字节数。如:

****************************************************************

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

int main(void)
{
A object;
cout << "sizeof(object) is " << sizeof(object) << endl;
return 0 ;
}

************************************************************

输出结果为12(我的机器上sizeof(float)值为4,字节对其前面已经讲过)。

不过需要注意的是,如果类中存在静态成员变量,结果又会是什么样子呢?

************************************************************

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

int main()
{
A object;
cout << "sizeof(object) is " << sizeof(object) << endl;
return 0 ;
}

************************************************************

16?不对。结果仍然是12.

因为在程序编译期间,就已经为static变量在静态存储区域分配了内存空间,并且这块内存在程序的整个运行期间都存在。

而每次声明了类A的一个对象的时候,为该对象在堆上,根据对象的大小分配内存。

如果类A中包含成员函数,那么又会是怎样的情况呢?看下面的例子

************************************************************

class A
{
public:
          static int a;
          int b;
          float c;
          char d;
          int add(int x,int y)
          {
            return x+y;
          }
};

int main()
{
A object;
cout << "sizeof(object) is " << sizeof(object) << endl;
b = object.add(3,4);
cout << "sizeof(object) is " << sizeof(object) << endl;
return 0 ;
}

************************************************************

结果仍为12。

因为只有非静态类成员变量在新生成一个object的时候才需要自己的副本。
所以每个非静态成员变量在生成新object需要内存,而function是不需要的。

11 应用实例

在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修改起来也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,其它程序无需修改,省时省力。下面以TCP协议首部为例,说明如何定义协议结构。其协议结构定义如下:

#pragma pack(1) // 按照1字节方式进行对齐
struct TCPHEADER 
{
     short SrcPort; // 16位源端口号
     short DstPort; // 16位目的端口号
     int SerialNo; // 32位序列号
     int AckNo; // 32位确认号
     unsigned char HaderLen : 4; // 4位首部长度
     unsigned char Reserved1 : 4; // 保留6位中的4位
     unsigned char Reserved2 : 2; // 保留6位中的2位
     unsigned char URG : 1;
     unsigned char ACK : 1;
     unsigned char PSH : 1;
     unsigned char RST : 1;
     unsigned char SYN : 1;
     unsigned char FIN : 1;
     short WindowSize; // 16位窗口大小
     short TcpChkSum; // 16位TCP检验和
     short UrgentPointer; // 16位紧急指针
}; 
#pragma pack() // 取消1字节对齐方式

Intel和微软和本公司同时出现的面试题

#pragma pack(8)

struct s1{
short a;
long b;
};

struct s2{
char c;
s1 d;
long long e;
};

#pragma pack()


1.sizeof(s2) = ?
2.s2的s1中的c后面空了几个字节接着是d?

正确答案:
如果代码:

#pragma pack(8)
struct S1{
    char a;
    long b;
};
struct S2 {
    char c;
    struct S1 d;
    long long e;
};
#pragma pack()

sizeof(S2)结果为24.
成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.
也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.

S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;

S2中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.

                            a      b

S1的内存布局:11**,1111,
c S1.a S1.b d
S2的内存布局:1***, 11**, 1111, ****11111111

这里有三点很重要:
1.每个成员分别按自己的方式对齐,并能最小化长度
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐

补充一下,对于数组,比如:
char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64…中的一个.

结构对齐是基础知识,这种问题只有一个目的,就是看你对语言细节的了解程度.
在网络通讯,多个模块交互的应用中这种东西是很常见.这一类的东西还有像__stdcall,__cdecl,volatile,mutable这些东西.平常学习中很难遇到,但实际工作中却会用.__stdcall, __cdecl参数传入的顺序从右到左,不同是前者修改的函数的参数stack从被调用者清除,而后者由调用者清除。所以后者编译出来的二进制代码比较前者大。volatile 定义的变量防止被编译器优化掉。

mutable定义的变量一般都非静态或者非常量类成员。它允许常量类成员对其赋值。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值