1. sizeof 简介
sizeof是一个关键字,不是一个函数,其作用是返回一个对象或者类型所占的内存字节数。
MSDN上的解释为:The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type (including aggregate types). This keyword returns a value of type size_t.
其返回值类型为size_t,在头文件stddef.h中定义。这是一个依赖于编译系统的值,一般定义为:typedef unsigned int size_t;
世上编译器林林总总,但作为一个规范,它们都会保证char、signed char和unsigned char的sizeof值为1,毕竟char是我们编程能用的最小数据类型。
2. sizeof用法
(1)基本类型
char:1 short:2 int:4 long:8 double:8 float:4
(不同机器不一样)
sizeof(2); // 2的类型为int,所以等价于 sizeof( int );
sizeof( 2 +3.14); // 3.14的类型为double,2也会被提升成double类型,所以等价于 sizeof( double );
(2)函数调用
sizeof也可以对一个函数调用求值(不是函数!!!),其结果是函数返回类型的大小,函数并不会被调用。
(3)指针
看机器,32位为4,64位为8。
(4)数组
数组的sizeof值等于数组所占用的内存字节数
char a1[] = "abc";
sizeof( a1 ); // 结果为4,字符末尾还存在一个NULL终止符
int a2[3];
sizeof( a2 ); // 结果为3*4=12(依赖于int)
注意:函数参数a3已不再是数组类型,而是蜕变成指针!
(4)union
结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值。
结构体的成员也可以是复合类型,这里,复合类型成员是被作为整体考虑的。
union U { int i; char c; }; //sizeof (U) == 4
(5)struct
不用说,最烦人的部分就是struct了,因为涉及字节对齐。
为什么需要字节对齐?
这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal padding);
3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
需要说明的是,1)和2)描述的就是前面所说,结构体成员的地址(由首地址和偏移地址决定)为其大小的整数倍。
常见例子:
1)struct S1 { char c; int i; };
对于准则1),编译器会将这个结构体的首地址位于int,也就是4的整数倍上,然后对于第一个成员char,占用1个字节,对于第二个成员int,根据准则2),偏移量应该为4,所以在char和int成员之间填充3个字节(0xCC)。所以总共占用8个字节,准则3)也实现了
2)struct S2 { int i; char c; };
同理,准则1)由编译器分配内存地址实现,然后准则2)此处无用,自动实现了。因为准则3),所以在char后面填充3个字节(0xCC)。
3)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(int的整数倍),c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13是不能被4整除的,这样末尾还得补上3个填充字节。最后得到sizeof(S3)的值为16。
3. sizeof和strlen区别
(1)sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
(2)sizeof是运算符,strlen是函数。
(3)strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0';如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。(不包含'\0');sizeof()函数返回的是变量声明后所占的内存数,不是实际长度。
(4)sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。
(5)数组做sizeof的参数不退化,传递给strlen就退化为指针了。
(6)大部分编译程序在编译的时候就把sizeof计算过了是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因。strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。
(7)sizeof 操作符不能返回被动态分派的数组或外部数组的尺寸
(8)strlen只能用char*做参数,且必须是以''\0''结尾的,而sizeof可用类型做参数,还可用函数做参数
关键点:
其实理解 sizeof 只需要抓住一个要点:栈
程序存储分布有三个区域:栈、静态和动态(堆)。能够从代码直接操作的对象,包括任何类型的变量、指针,都是在栈上的;动态和静态存储区是靠栈上的所有指针间接操作的。
sizeof 操作符,计算的是对象在栈上的投影体积;记住这个就很多东西都很清楚了。
需要说明的是:
sizeof的计算发生在编译时刻,所以可以当作常量表达式来使用(C99标准规定sizeof也可以在运行时刻进行计算)
C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值
编译器的pack指令用来调整结构体对齐方式的,不同编译器名称和用法略有不同