C/C++的sizeof()总结
一、定义
sizeof是C/C++中的一个单目操作符(而非函数),其作用是返回一个对象或者类型所占的内存字节数
二、使用方法
-
用于数据类型
使用形式:sizeof(type)
数据类型必须用括号括住:如sizeof(int) -
用于变量
使用形式:sizeof(var_name) 或sizeof var_name
变量名可以不用括号括住:如sizeof(var_name),sizeof var_name都是正确形式
注意:
-
sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值都是一致的。sizeof对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,一般不会对表达式进行计算
-
sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用
-
函数、不能确定类型的表达式(未知存储大小的数组类型、位置内容的结构或联合类型、void类型)以及位域(bit-field)成员不能被计算sizeof值
三、strlen()与sizeof()的区别
-
strlen(char*)函数求的是字符串的实际长度,直到遇到第一个'\0',然后就返回计数值,且不包括'\0'。
而sizeof()函数返回的是变量声明后所占的内存数,不是实际长度。 -
sizeof是算符,strlen是函数。
-
sizeof可以用类型或函数做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。
-
数组做sizeof的参数不退化,传递给strlen就退化为指针了。
-
大部分编译程序在编译的时候就把sizeof计算过了是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。
四、基本数据类型的sizeof
C/C++中不同数据类型所占用的内存大小(字节)
32bit | 64bit | |
---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
long | 4 | 8 |
float | 4 | 4 |
double | 8 | 8 |
五、指针变量的sizeof
在32位计算机中,一个指针变量的返回值通常是 4(字节),在64位系统中指针变量的sizeof通常为 8(字节)
指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等,所以MFC消息处理函数使用两个参数WPARAM、LPARAM就能传递各种复杂的消息结构(使用指向结构体的指针)。
六、数组的sizeof
数组的sizeof值等于基础元素大小乘以元素的个数,如:
char a1[]="abc";
sizeof(a1); //结果为4,字符末尾还存在一个'\0'终止符
int a2[3];
sizeof(a2); //结果为3*4 = 12,依赖于int
求数组元素个数的两种写法:
int c1=sizeof(a1)/sizeof(char); //总长度/单个元素长度
int c2=sizeof(a2)/sizeof(a2[0]); //总长度/第一个元素长度
七、结构体的sizeof
结构体(struct)是一种复合数据类型,其构成元素既可以是基本数据类型,也可以是复合数据类型(如数组、struct、union等)
如下面的数据结构:
struct s{
int x1;
char x2;
int x3;
};
用sizeof求该结构体的大小,发现值为 12 ,而非 4+1+4 = 9。这是因为:
当操作数是结构类型时,sizeof是将所有成员字节对齐后的长度之和
-
在结构体中,编译器为结构体的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。和数组不一样的是,结构体的大小不是所有成员大小简单的相加,需要考虑到系统在存储结构体变量时的地址对齐问题。
-
字节对齐:也称字节填充,它是C/C++编译器的一种技术手段,主要是为了在空间与复杂度上达到平衡。简单地讲,是为了在可接受的空间浪费的前提下,尽可能地提高对相同运算过程的最少(快)处理。字节对齐的作用不仅便于 CPU 的快速访问,使 CPU 的性能达到最佳,还通过合理地利用字节对齐来有效地节省存储空间。
例如,32 位计算机的数据传输值是 4 字节,64 位计算机的数据传输值是 8 字节,这样,在默认的情况下,编译器会对 struct进行(32 位机)4 的倍数或(64 位机)8 的倍数的数据对齐。对于 32 位机来说,4 字节对齐能够使 CPU 访问速度提高,例如一个 long 类型的变量,如果跨越了 4 字节边界存储,那么 CPU 要读取两次,这样效率就低了,但需要注意的是,如果在 32 位计算机中使用 1 字节或者 2 字节对齐,不仅不会提高效率,反而会使变量访问速度降低。
字节对齐的细节和编译器实现相关,但一般满足以下三个准则:
- 结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
- 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要,编译器会在成员之间加上填充字节。
- 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节。
偏移量:偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。显然,结构体变量中第一个成员的地址就是结构体变量的首地址。
因此,上面的例子中结构体 s 的第一个成员 x1 的偏移量为 0。第二个成员 x2 的偏移量是第一个成员的偏移量加上第一个成员的大小(0+4),其值为 4;第三个成员 x3 的偏移量是第二个成员的偏移量加上第二个成员的大小(4+1),其值为 5,但并不是自身(int)大小的整数倍。编译器在处理时会在第二个成员后面补上 3 个空字节,使得第三个成员的偏移量变成8。结构体大小等于最后一个成员的偏移量加上其大小,上面的例子中计算出来的大小为 12 。
对比下面两种定义顺序:
struct s1{
char x1;
int x2;
char x3;
};
struct s2{
char x1;
char x2;
int x3;
};
虽然结构体 s1 和 s2 中成员都一样,但 sizeof(struct s1) 的值为 12,而sizeof(struct s2)的值为 8 。
由此可见,结构体类型需要考虑到字节对齐的情况,不同的顺序会影响结构体的大小。
题目1:
struct s3{
char x1;
short x2;
float x3;
char x4;
};
由于编译器默认情况下会对 struct 进行边界对齐:
- 结构体的第一个成员 x1,其偏移地址为0,占据了第 1 个字节;
- 第二个成员 x2 为 short 类型,其起始地址必须 2 字节对齐,因此,编译器在 x2 和x1之间填充了一个空字节。
- 第三个成员 x3 和第四个成员 x4 恰好落在其自然边界地址上,在它们前面不需要额外的填充字节。在 s1 结构体中,成员 x3 要求4 字节对齐,是该结构所有成员中要求的最大边界单元,因而 s3 结构的自然对界条件为 4 字节,所以编译器在成员 x4 后面填充了 3 个空字节。
- 整个结构所占据空间为 12 字节。
题目2:
struct s4{
short d;
int a;
short b;
};
在 32 位计算环境下,short 型占 2 个字节,int 型占 4 个字节,所以,为了满足字节对齐的要求,变量 d 除了自身所占用的 2 个字节外,还需要再填充 2 个字节,变量 a 占用 4 个字节,变量 b 除了自身占用的 2 个字节,还需要 2 个填充字节,所以,最终 s4 的 sizeof 值为 12。
题目3 嵌套结构体
由于结构体的成员可以是复合类型的,因此在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看作一个整体,例如,一个结构体中包含另外一个结构体成员,那么此时最宽基本类型成员不是该结构体成员,而是取基本类型的最宽值。但在确定复合类型成员的偏移位置时,则是将复合类型作为整体看待,即复杂类型(如结构体)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度,达到程序优化的目的。
对于嵌套的结构体,需要将其展开。原则:
-
展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍。
-
结构体大小必须是所有成员大小的整数倍,这里所有成员计算的是展开后的成员,
而不是将嵌套的结构体当做一个整体。
struct s5 {
short x1;
struct
{
char x2;
int x3;
}ss5;
int x4;
} ;
结构体 s5 的成员 ss5.x2 的偏移量应该是 4,而不是 2。整个结构体大小应该是 16。
struct s6{
char x1;
struct
{
char x2;
int x3;
}ss6;
char x4;
char x5;
char x6;
char x7;
char x8;
} ;
结构体 ss6 单独计算占用空间为 8,而 s6 的 sizeof 则是 20,不是 8 的整数倍,这说明在计算 sizeof(struct s6) 时,将嵌套的结构体 ss6 展开了,这样 s6 中最大的成员为 ss6.x3,占用 4 个字节, 20 为 4 的整数倍。结构体s6大小为20。如果将ss6当做一个整体,结果应该是24。
struct s7
{
float x1;
char x2;
int arr[3];
};
float占4个字节,到char x2时偏移量为4,x2占一个字节,到int aarr[3]时偏移量为5,扩展为int的整数倍,而非int arr[3]的整数倍,这样偏移量变为8,而不是12。结果是8+12=20,是最大成员float或int的大小的整数倍。结构体s6大小为20。
八、联合体的sizeof
结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值的对齐结果。如:
union u{
char c;
double d;
}u;
sizeof(u) = max(sizeof(c),sizeof(d)) = sizeof(1,8) = 8;