概述
sizeof这个知识点几乎是程序员找工作笔试必考的题目。已经遇到过几次,深受其害,有必要总结一下了。
sizeof是C/C++中的关键字,它是一个运算符,其作用是取得一个对象(数据类型或者数据对象)的内存长度(以byte为单位)。其中类型包含基本数据类型(不包括void)、用户自定义类型(结构体、类)、函数类型。数据对象是指用前面提到的类型定义的普通变量和指针变量(包含void指针)。不同类型的数据的大小在不同的平台下有所区别,但是c标准规定所有编译平台都应该保证sizeof(char)等于1。
sizeof的几大用法:
- sizeof是运算符,不是函数;
- sizeof object; //ok
- sizeof(object); //ok
- sizeof(typename); //ok
- sizeof typename ;//false
- sizeof不能求得void类型的长度;
- sizeof能求得void类型的指针的长度;
- sizeof能求得静态分配内存的数组的长度!
- sizeof求动态分配的内存数组的大小需谨慎!
如果该动态数组是malloc、new方式分配,无法求数组的大小。有一种情况例外,如下
//该代码在支持c99的codeblocks中可以执行
#include <iostream>
using namespace std;
int main()
{
int len;
cin>>len;
int arr[len];
cout<<sizeof(arr);//可以求数组大小
return 0;
}
- sizeof不能对不完整的数组求长度;
- 当表达式作为sizeof的操作数时,它返回表达式的计算结果的类型大小,但不对表达式求值;
- sizeof可以对函数调用求大小,并且求得的大小等于返回类型的大小,但不执行函数体;
sizeof在求类和结构体的大小时并不总是等于各个成员数据的大小之和;
这涉及到类和结构体成员数据内存对齐的规则。这些规则说复杂,但说简单也很简单,只需记住三句话:
- 结构体的大小等于结构体内最大成员大小的整数倍
- 结构体内的成员的首地址相对于结构体首地址的偏移量是其类型大小的整数倍
- 为了满足规则1和2编译器会在结构体成员之后进行字节填充
sizeof求类大小举例
1. 各成员变量的类型都相同时(可含数组且数组数据类型同类中其他成员数据类型)
- 结构体长度=成员数据类型长度×成员个数;
- 结构体中数组长度=数组数据类型长度×数组元素个数;
class one
{
double d1;
double d2[3];
};//sizeof(one)为32B
2. 成员不同且不含其它结构体时;
- 分析各个成员长度;
- 找出最大长度的成员长度M(结构体的长度一定是该成员的整数倍);
- 并按最大成员长度出现的位置将结构体分为若干部分;
- 计算每个部分长度。求出大于该部分所有变量长度之和的最小M的整数倍即为该部分长度
- 将各个部分长度相加之和即为结构体长度
class A{
char a;
double b;
int c;
};
//a、b、c的长度分别为1、8、4,以最长的8为界分为三组:a为一组,取8大于1的最小倍数,为8。
//第二部分只有b,大小为8B。第三部分只有c,取8大于4的最小倍数,即为8。sizeof(A)=8+8+8=24B。
class B{
char a;
int b;
double c;
};
//a、b、c的长度分别为1、4、8,以最长的8为界分为两组,a、b为一组,和为5,取8大于5的最小倍数,为8。
//第二部分只有c,所以sizeof(B)=8+8=16B。
3.含有其他类时:
- 分析各个成员长度(包括其他类成员的长度);
- 其他类成员的长度不会随着位置的变化而变化;
- 找出各个成员的长度最大值(包含其他类和结构体的成员);
- 若长度最大成员在为结构体的成员中,则按结构体成员为分界点分界;
- 其他成员中有最大长度的成员,则该成员为分界点;
- 求出各段长度,求出大于该和的最小M的整数倍即为该部分长度
class A{
char a;
double b;
int c;
};//sizeof(A)为24B
class C{
char a;
A obj;
int b;
double c;
};//a、b、obj、c的长度分别为1、24、4、8;最长的成员为8,所以以obj和c为界。
//每部分都取大于各部分和的8的最小倍数。sizeof(C)=8+24+8+8=48B
4.空结构体和空类型的大小计算。含有虚函数的类的大小计算。
sizeof(空类)和sizeof(空结构体)的大小都为1。具体细节见sizeof(空类)问题总结
- sizeof不能用于求结构体的位域成员的大小,但是可以求得包含位域成员的结构体的大小;
位域
在存储信息的时候,可能并不需要占用一个完整的字节,而只需占1bit或3bit就够了,比如存一个八进制数据,只需3bit就行;在存放一个开关量时,只有0和1 两种状态,用一位二进位即可。为了节约存储空间,C/C++提供了位域这种数据结构。所谓位域,就是把存储空间中的二进制位划分为几个不同的区域,并说明每个区域的位数,每个域有一个域名,在程序中允许按域名进行操作。
定义位域的一般形式如下:
/*
struct [位域结构名] //和结构体的定义几乎相同,用class也可以
{
typename 位域名:位域长度;
typename 位域名:位域长度;
……
typename 位域名:位域长度;
}[位域变量];
位域变量的声明与结构变量声明的方式相同。可采用先定义后声明、同时定义声明或者直接声明这三种方式。
*/
struct _data1
{
char a:6;
char b:2;
char c:7;
};//大小占2B
struct _data2
{
char a:6;
char b:4;
char c:7;
};//占3B
struct _data3
{
char a:6;
int b:22;
char c:7;
};//字节对齐为4B,位域结构体大小占12B
struct _data4
{
int a:22;
char b:6;
char c:7;
};//字节对齐为4B,位域结构体大小占8B
#pragma pack(2)
struct _data5
{
int a:16;
char b:4;
char c:6;
};//6B
#pragma pack()//# pragma pack( )即可消除之前设置的字节对齐方式
关于#pragma 命令,详见#pragma指令的使用
位域结构体大小计算方法:(位域的使用,在这里不作为重点展开)
- 当相邻位域的类型相同时。如果其位宽之和小于该类型所占用的位宽大小,那么后面的位域紧邻前面的位域存储。如果其位宽之和大于该类型所占用的位宽大小,那么后面的位域就从下一个存储单元开始存储。
- 当相邻位域的类型不同时。不同编译器处理方式可能不同,以VC++ 6.0为准。默认情况下,位域结构中的字节对齐方式由其中占用字节数最大的类型所决定。以上面的struct _data3为例说明,首先保存6个位域长度的a,因为b与a类型不同,不能存储在同一个字节中,还需要为b寻找合适的起始地址,接下来22bit用来存b。因为c类型与b不同,因此c不能在int型变量所占用的空间中存位域c。最终位域结构的大小必是4的整数倍。
- 使用# pragma pack(x)命令,强制使用x字节对齐方式。再使用一次# pragma pack( )即可消除之前设置的字节对齐方式。关于# pragma 的详细讲解,见下面的链接:
- C语言允许省略位域的名字。未命名的位域经常用来作为字段间的填充,以保证其他位域存储在适当的位置。无名的位域是不能使用的。
重要参考
1.《 C语言程序设计现代方法(第2版)》 K.N. King著
2.C/C++刁钻问题各个击破之细说sizeof
3. 数据对齐与sizeof()
4. 《C语言进阶 重点、难点与疑点解析》 牟海军 著