C语言多少个关键字? sizeof是函数吗?是否呆住了?
今天就看下sizeof的方方面面
首先,觉得不论是看源码还是探索什么,首先一定要带着问题来探索才最有效的,所以这里先说出几个关于sizeof的问题
1 sizeof是函数吗?
2 sizeof与strlen有什么区别?
3 sizeof求某个结构等等之类的大小,别忘了对齐哦。
4 能用一个宏实现sizeof吗?只需实现 sizeof(type)及sizeof(var),即实现数据类型(例如int,float,char等)及变量?
问题1 sizeof是函数吗?
我们要肯定的说 NO,他是C语言中保留的32个关键字之一。作用就是返回一个对象或者类型所占的内存字节数。根据MSDN上的解释是:
The sizeof keyword gives the amount of storage, in bytes,associated with a variable or a type (including aggregatetypes). 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是我们编程能用的最小数据类型。
看到没有,sizeof是 keyword,那么既然是关键字,那为什么需要加上括号呢?其实你可以试一下你输出
int i=1;
sizeof i;
其结果与sizeof(int)一样都是4,这就说明sizeof不可能是函数,因为函数一定要有括号。那为什么都不加括号呢? 其实下文会说到他的实现应该是一个宏,那么联想我们平时在对宏的说法中,是不是总是强调不要吝啬括号。所以我猜想应该是有这个考虑吧。另外,如果输出sizeof int 却不会像sizeof 4那样顺利,却会提示一个 error C2062: type 'int' unexpected 。想想C/C++语言的规定,int前只能加上signed.unsigned,,const等来修饰变量的存储方式,可没有提到前面可以加sizeof啊,如果前面加sizeof表示是什么存储方式呢?
问题2 sizeof与strlen有什么区别?
sizeof是关键字,strlen是一个标准C语言库函数,用来求取字符串的长度,char *str="abacd",sizeof str与strlen(str),编译一下,一个结果是4,一个结果是5,结果为4是因为一个指针占4字节,结果为5是因为串长abacd刚好5个字符(注意strlen计算的长度不包括最后的结束符'\0')
问题3 sizeof求某个结构等等之类的大小,别忘对齐
这个问题林林总总网上就会有一大片了啊,下面就只说几种更容易出错的情况,也当是给自己一个复习
char *p="abcdef";
char a[100]={0};
int *pl=NULL;
printf("%d\n",sizeof p);
printf("%d\n",sizeof(int)(*p));
printf("%d\n",sizeof(pl));
printf("%d\n",sizeof a);
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(a[100]));
printf("%d\n",sizeof(&a[0]));
先不看下面的输出,你认为是输出什么呢?
答案如下:
一个一个来,对于第一个,在上面其实已经说到了,因为p的是一个指针变量,所以其大小应该是一个int型,4个字节(我做测试是在32位机上的VC++),而对于第二个,主要在于理解 ,这里相当于将指针变量p强制转化为int型 也就是 (init)(*p) 然后在对其取sizeof。而对于第三个,尽管我们将指针变量pl初始化为null(这是好习惯,避免野指针),但是sizeof(p)问的依旧是存放指针的地方大小是多大,跟第一个是一样的。第四个sizeof a 好理解一些,这里定义了一个100个int型的数组,取名a(想想他就是个变量名,只是这个变量比较大个头,由100个int组成,想想上一篇中说到的模子),那么sizeof a就是问这个数组的大小,当然是 100*sizeof(char)。 而第5个sizeof(&a),这里要理解的是 a是数组名,但是要注意a不是数组的首地址,而是&a(关于这点后面在数组跟指针的文章中会说到)。所以这里sizeof(&a)还是整个数组的大小为100*sizeof(char).
对于sizeof(a[100]) 尽管他已经不再是这个数组的范围,但是别忘了sizeof是编译器内置的一个宏,他在预编译时候已经计算出来了,那时候他根本也不会查看是否已经越界等,他只知道a的一个char型数组,而a[100]是一个元素,所以就是sizeof(char) 而对于最户的&a[0]当然很好理解,他就是a[0]这个元素的地址,当然就是一个指针那样的大小4bytes
再来看看下关于结构体对齐的:
struct TryNull
{
}tn;
struct Try1
{
int i;
int j;
char c;
}t1;
struct Try2
{
char *p;
int i;
}t2;
typedef struct
{
char c[2];
int i;
static int j;
}Try3;
typedef struct
{
int i;
int a[0];
}Try4;
int main(int argc, char* argv[])
{
Try3 *p3=(Try3*)malloc(sizeof(Try3));
Try4 *p4=(Try4*)malloc(sizeof(Try4)+100*sizeof(int));
printf("%d\n",sizeof(tn));
printf("%d\n",sizeof(t1));
printf("%d\n",sizeof(t2));
printf("%d\n",sizeof(Try3));
printf("%d\n",sizeof(Try4));
printf("%d\n",sizeof(*p3));
printf("%d\n",sizeof(*p4));
return 0;
}
还是先思考下自己的答案,再来 对下面的:
问题4 空结构体多大?
例如第一个输出所示,不是0,而是1,根据前边数据结构的模子概念,编译器认为任何一种数据类型都有其大小,用它来定义一个变量能够分配确定大小的空间。既然如
此,编译器就理所当然的认为任何一个结构体都是有大小的,哪怕这个结构体为空。而究竟给多大呢?一个非空的最小的结构体应该是只有一个char性,大小为1,所以空结构体不能比非空结构体还要小吧,但内存的最小单位也是1byte,所以空结构体也只能取最小的1byte了。
问题5 对齐不仅仅是结构体内单个数据成员的对齐,还要考虑最后整个结构体
一定要注意,结构体的对齐包含两方面。
1)结构体内各数据成员的内存对齐,即该数据成员相对结构体的起始位置,这里都对32位机子来说,例如,
对于char型,他的起始首地址为sizeof(char)=1的倍数,
int型起始首地址为sizeof(int)=4的倍数。
short型起始首地址是2的倍数
double 型起始首地址是8的倍数。
2) 结构体总长度
对于2)表示对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。所以对于上面的try1,try2就知道其大小了吧。
而对于try3,要注意static变量是在静态区,在算结构体大小时候是不算的。我自己的想法是这样的,我们所说的sizeof等这些大小,应该都是指我们在内存堆上是申请的一个地方,所以static在静态区不算。
另外,要注意有些题目中如果有指定对齐值:#pragma pack (value)时指定的对齐value,这时候是这样处理的:对每个成员在处理时候,看他按上面的规则1)算出的对齐的模,然后与#pragma pack (value)比较取小的作为单个成员的对齐标准;
在最后处理整个结构体时候,还是按照上面的2)算出整个struct对齐的模,与#pragma pack (value)比较取小的作为整个struct的对齐标准;所以这里注意在利用2)中是取各个成员中对齐模的最大值与#pragma pack (value)比较,再取最小值
例如:
//分析下面的例子C:
//
#pragma pack (2) /*指定按2字节对齐*/
structTry
{
char b;
int a;
short c;
};
#pragma pack () //恢复对齐状态
/*
第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设Try从0x0000开始,那么b存放在0x0000,符合0x0000%1 = 0;
第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续的字节空间中,符合0x0002%2=0。
第三个变量Try的自身对齐值为2,所以有效对齐值为2,顺序在0x0006、0x0007中,符合0x0006%2=0。
所以从0x0000到0x0007共八字节存放的是struct Try的变量。又struct Try的自身对齐值为4,所以struct Try的有效对齐值为2。
又8%2=0,struct Try只占用0x0000到0x0007的八个字节。所以sizeof(struct Try)=8。
如果把上面的#pragma pack(2)改为#pragma pack(4),那么我们可以得到结构的大小为12。
*/
问题6 结构体中的柔性数组
对于上面的try4,可能你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员。例如在内核源码中你是可以看到的,在内核的MD模块中在表示阵列超级块的结构中就有类似 int roles[0]的成员,因为我们开始不知道我们会使用几个盘来创建阵列,所以他就使用了柔性数组来表示组成阵列的几个盘在阵列中的角色。柔性数组成员允许结构中包含一个大小可变的数组。sizeof返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
正如我们说的sizeof这种结构大小不包含柔性数组的内存,所以上面的try4大小仅仅包含int 成员,而不包含柔性数组。但一定要注意的是最后一个的输出,我们利用Try4 *p4=(Try4*)malloc(sizeof(Try4)+100*sizeof(int));为这个结构体分配一块内存,我们也可以通过p->a[i]来访问柔性数组,但是这时候我们再用sizeof(*p4)测试结构体的大小,发现仍然为4,再来想一下我们说的数据结构模子。在定义这个结构体的时候,模子的大小就已经确定不包含柔性数组的内存大小。柔性数组只是编外人员,不占结构体的编制。只是说在使用柔性数组时需要把它当作结构体的一个成员,仅此而已。再说白点,柔性数组其实与结构体没什么关系,只是“挂羊头卖狗肉”而已,算不得结构体的正式成员。
问题6 对齐有何原因及好处?
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的起始地址的值是某个数k的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus),如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。
问题7:用宏实现sizeof
首先再次要明确sizeof是什么
1,它是个运算符;
2,编译器具体实现的时候,大多数是个系统宏。
比如:
#define sizeof(type) \
switch(type){case int:4;char:1;………………}
当然switch语句只是比喻,这个运算符不是在运行的时候进行运算,而是在预处理阶段就算好了。这点很容易理解,就像#define A (3*4),编译器在预编译阶段就自动替换为12。
那么,如果让我们自己实现一个宏来实现sizeof?该宏能够实现 sizeof(type)和sizeof(var)两个功能, type为基本类型,var 为变量.这个时候就应该考虑下地址偏移及数据模子了。我们知道sizeof最后是以bytes计算。所以考虑要计算的东西var,那么就取var下一个元素模子与var这个模子的地址差就对了,如下:
#define SIZEOF(var) ((char*)(&var+1)-(char*)&var)
#define SIZEOF(type) ( (char *)((type *)0 + 1) - (char *)0 )
第二个是表示将0这个数字(可以换为其他)强制转换为type型的指针,那就跟第一个是一样的意思了!