关键字之sizeof
sizeof哭着说:“你真的了解我吗?”。。。
对于sizeof这个关键字,其实我猜大家都知道一点,因为c语言初期就会接触到它,但是又了解的不是很透彻,对于基本数据类型,指针,数组,结构体,函数,联合体,类,位域成员,位域结构体等的字节计算能搞清楚吗?没事,往下看。
一.定义
首先:sizeof运算符的类型为size_t(size_t在头文件stddef.h中定义),它依赖于编译系统的值,此运算符使您可以避免指定设备相关的数据范围在程序。
其次:它的作用是范围一个对象或类型所占的内存字节。
最后:明确一点sizeof是一个操作符,不是一个函数。(众所周知,函数后面必须要有括号里面是形参,用于我们调用函数,但sizeof的括号可以省略,所以恰恰证明了sizeof并不是一个函数。)
(对啦,说一下,我加的stdlib.h头文件和system(“pause”)是为了把输出框留住,你们不用加哦,yeahyeah)
二.语法
sizeof有三种语法形式:
1) sizeof (object); //sizeof (对象)
2) sizeof object; //sizeof 对象
3) sizeof (type_name); //sizeof (类型)
对象可以是各种类型的变量,以及表达式。
我们一般习惯于第一种,第三种形式,虽然第二种形式是正确的,但是不常使用,所以我们一般见到的sizeof都是带括号的,也因此很多同学误以为sizeof是个函数(error)。
三.基本数据类型的sizeof
对于基本数据类型的sizeof运算,大家看到下面的图应该就明白了。(我的系统是32位,64位下long的8字节)
四.表达式的sizeof
对于表达式的sizeof运算我们先抛出一个问题,大家可以先想想。
#include<iostream>
#include<stdlib.h>
using namespace std;
int main()
{
int a = 10;
cout<<sizeof (a++)<<endl;
cout<<sizeof (++a)<<endl;
system("pause");
return 0;
}
你有结果了吗?
好啦!答案揭晓:两个输出结果都是4.可是为什么呢?
我们来看看反汇编代码吧
大家可以看到我红色圈出的部分,果然传参的时候是直接push了4,而a++这个指令在没有在汇编中出现的痕迹。
但是sizeof操作符并不像#define这样的宏一样在预处理阶段就把其替换掉了,sizeof是在编译阶段替换的。(同理++a是一样的,大家下去可以验证)
于是理解了,sizeof里面的表达式都是不执行的,只关心里面类型的大小。
五.指针类型的sizeof
大家都知道指针是一个很复杂,很重要的部分,但是指针对于sizeof运算却是非常的简单,大家只要记住32位下不管什么类型的指针在内存中只占四个字节,64位中占八个字节(任何类型指针哦)。
六.数组的sizeof
数组的sizeof值等于数组所占用的内存字节数。
我们来看一个问题:
#include<iostream>
#include<stdlib.h>
using namespace std;
void func(char a[])
{
int c = sizeof( a );
cout<<c<<endl;
}
int main()
{
char a[3];
int array[3];
char ptr1[] = "hallo";
char *ptr2 = "hallo";
cout<<sizeof (ptr1)<<endl; //6
cout<<sizeof (ptr1[2])<<endl; //1
cout<<sizeof (ptr2)<<endl; //4
cout<<sizeof (array)<<endl; //12
cout<<sizeof (array[2])<<endl; //4
func(a); //4
system("pause");
return 0;
}
按顺序:第一个输出为6,因为是char类型,大家有疑问hello是五个字节吗?对啦,还有一个终止符,即“\0”。第二个输出为1,因为ptr1这个数组一共有五个元素,ptr1[2]则指第三个元素占几个字节,由char类型可知为一个字节。第三个输出为4,因为ptr2是个数组指针,前面我们说过,不管什么类型指针在32位系统下都是四个字节。第四个输出为12,因为数组名代表整个数组,数组的sizeof值等于数组所占用的内存字节数,所以3*4=12.第五个输出为4,和第二个相似,只取决于类型,因为是int,所以输出四个字节。第六个输出为4,因为形参中的数组退化为一个char*的指针,所以我们就等于在对一个指针求sizeof,当然是4啦。
数组的长度的计算公式:
Int len = sizeof(arr)/ sizeof( arr[ 0 ] );
七.结构体的sizeof
sizeof在结构体部分的运算应该是最让初学者头疼的问题,那么我们依然来看一个简单的例子:
怎么样?是不是很自信的觉得是5,nonono..那只是你觉得,对于8这个结果,那我们就必须提一下结构体内存字节对齐原则啦!
结构体内存对齐原则:
关于结构体内存对齐(在没有#pragma pack宏的情况下) :
•原则1
数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
看到这个规则,你们是不是想如果int和char倒一下位置是不就好啦,那我们来试一下。
no~真相又再一次不像你以为的那样。所以,继续往下看吧。
•原则2
结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素数据类型的字节大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
第二个原则看完明白了吧,并不是倒一下两个类型就可以完成的,人家在意的是这些类型中占内存最大的哪一个啦。。。
•原则3
也就是说, 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
八.联合体的sizeof
联合体在内存中是重叠式,也就是说它的内存是最大数据类型所占的字节。
所以这里整个联合体所占的字节数就是最大的数据类型double所占的字节数,为8
利用union的特性来判断大小端:
#include<iostream>
using namespace std;
union U
{
int a;
char b;
}u;
int main()
{
U u;
u.b = 1;
if(u.a == 1)
{
cout<<"小端"<<endl;
}
else
cout<<"大端"<<endl;
return 0;
}
首先我们了解一下联合体的特征:在union中所有的成员共用同一个空间,同一时间只存储一个数据成员,最大的特征就是所有的数据成员具有相同的起始地址即联合体的基地址。
计算机中字节存储主要有两种:大端模式和小端模式,大端模式是从低地址开始,高位结束;小端模式是从高地址开始,低地址结束。
利用union中所有数据成员具有同样的起始地址的特点,通过一个int成员存储1,然后通过char成员来读取,即可巧妙地得出数据存放的方式,若通过char成员(即读取起始位置上的第一个字节)读取,若得出值为1,则说明是小端模式。
九函数的sizeof
sizeof可以对函数调用取值,结果为返回值的字节大小,但是函数本身并不会被调用。因为函数的sizeof结果为返回值的字节,所以在Windows下返回值为void的函数取sizeof会报错。
#include<iostream>
using namespace std;
void fun()
{
}
int fun1(int a,int b)
{
return a+b;
}
int main()
{
int a = 1;
int b = 2;
//cout<<sizeof(fun())<<endl;
cout<<sizeof(fun1(a,b))<<endl;
return 0;
}
而在gcc中,返回值为void的可以编译成功,结果为1,大家可以自己试试。
十.类的sizeof
在C++ 中struct 和class都是类,它们的唯一区别就是strcut 是默认公有(public)限定,class默认私有(private)限定。还有,我们应该清楚的是在c++中空struct和空class的大小都为1字节。
在虚函数和纯虚函数中,会产生一个指向虚函数表的指针(vfptr),所以字节数要加上指针四个字节。只有一个虚函数或纯虚函数的类有四个字节。
在继承中,如果B继承了A,那么B也会继承A里所有的成员变量,加上自己的成员变量的字节数就是B的内存大小。
如下图:
没有继承关系:
有继承关系: