C++ sizeof利用法则及陷阱阐明
副标题#e#
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.
看到return这个字眼,是不是想到了函数?错了,sizeof不是一个函数,你见过给一个函数传参数,而不加括号的吗?sizeof可以,所以sizeof不是函数。网上有人说sizeof是一元操纵符,可是我并不这么认为,因为sizeof更像一个非凡的宏,它是在编译阶段求值的。举个例子:
cout<<sizeof(int)<<endl; // 32位机上int长度为4
cout<<sizeof(1==2)<<endl; // == 操纵符返回bool范例,相当于 cout<<sizeof(bool)<<endl;
在编译阶段已经被翻译为:
cout<<4<<endl;
cout<<1<<endl;
这里有个陷阱,看下面的措施:
int a = 0;
cout<<sizeof(a=3)<<endl;
cout<<a<<endl;
输出为什么是4,0而不是期望中的4,3???就在于sizeof在编译阶段处理惩罚的特性。由于sizeof不能被编译成呆板码,所以sizeof浸染范畴内,也就是()内里的内容也不能被编译,而是被替换成范例。=操纵符返回左操纵数的范例,所以a=3相当于int,而代码也被替换为:
int a = 0;
cout<<4<<endl;
cout<<a<<endl;
所以,sizeof是不行能支持链式表达式的,这也是和一元操纵符纷歧样的处所。
结论:不要把sizeof当成函数,也不要看作一元操纵符,把他当成一个非凡的编译预处理惩罚。
#p#副标题#e#
2、sizeof的用法
sizeof有两种用法:
(1)sizeof(object)
也就是对工具利用sizeof,也可以写成sizeof object 的形式。
(2)sizeof(typename)
也就是对范例利用sizeof,留意这种环境下写成sizeof typename是犯科的。下面举几个例子说明一下:
int i = 2;
cout<<sizeof(i)<<endl; // sizeof(object)的用法,公道
cout<<sizeof i<<endl; // sizeof object的用法,公道
cout<<sizeof 2<<endl; // 2被理会成int范例的object, sizeof object的用法,公道
cout<<sizeof(2)<<endl; // 2被理会成int范例的object, sizeof(object)的用法,公道
cout<<sizeof(int)<<endl;// sizeof(typename)的用法,公道
cout<<sizeof int<<endl; // 错误!对付操纵符,必然要加()
可以看出,加()是永远正确的选择。
结论:岂论sizeof要对谁取值,最好都加上()。
3、数据范例的sizeof
(1)C++固有数据范例
32位C++中的根基数据范例,也就char,short int(short),int,long int(long),float,double, long double
巨细别离是:1,2,4,4,4,8, 10。
思量下面的代码:
cout<<sizeof(unsigned int) == sizeof(int)<<endl; // 相等,输出 1
unsigned影响的只是最高位bit的意义,数据长度不会被改变的。
结论:unsigned不能影响sizeof的取值。
(2)自界说数据范例
typedef可以用来界说C++自界说范例。思量下面的问题:
typedef short WORD;
typedef long DWORD;
cout<<(sizeof(short) == sizeof(WORD))<<endl; // 相等,输出1
cout<<(sizeof(long) == sizeof(DWORD))<<endl; // 相等,输出1
结论:自界说范例的sizeof取值等同于它的范例原形。
(3)函数范例
思量下面的问题:
int f1(){return 0;};
double f2(){return 0.0;}
void f3(){}
cout<<sizeof(f1())<<endl; // f1()返回值为int,因此被认为是int
cout<<sizeof(f2())<<endl; // f2()返回值为double,因此被认为是double
cout<<sizeof(f3())<<endl; // 错误!无法对void范例利用sizeof
cout<<sizeof(f1)<<endl; // 错误!无法对函数指针利用sizeof
cout<<sizeof*f2<<endl; // *f2,和f2()等价,因为可以看作object,所以括号不是须要的。被认为是double
结论:对函数利用sizeof,在编译阶段会被函数返回值的范例代替,
4、指针问题
思量下面问题:
cout<<sizeof(string*)<<endl; // 4
cout<<sizeof(int*)<<endl; // 4
cout<<sizof(char****)<<endl; // 4
可以看到,不管是什么范例的指针,巨细都是4的,因为指针就是32位的物理地点。
结论:只要是指针,巨细就是4。(64位机上要酿成8也不必然)。
顺便唧唧歪歪几句,C++中的指针暗示实际内存的地点。和C纷歧样的是,C++中打消了模式之分,也就是不再有small,middle,big,取而代之的是统一的flat。flat模式回收32位实地点寻址,而不再是c中的 segment:offset模式。举个例子,如果有一个指向地点 f000:8888的指针,假如是C范例则是8888(16位, 只存储位移,省略段),far范例的C指针是f0008888(32位,高位保存段地点,职位保存位移),C++范例的指针是f8888(32位,相当于段地点*16 + 位移,但寻址范畴要更大)。
5、数组问题
思量下面问题:
#p#分页标题#e#
char a[] = "abcdef";
int b[20] = {3, 4};
char c[2][3] = {"aa", "bb"};
cout<<sizeof(a)<<endl; // 7
cout<<sizeof(b)<<endl; // 20*4
cout<<sizeof(c)<<endl; // 6
数组a的巨细在界说时未指定,编译时给它分派的空间是凭据初始化的值确定的,也就是7。c是多维数组,占用的空间巨细是各维数的乘积,也就是6。可以看出,数组的巨细就是他在编译时被分派的空间,也就是各维数的乘积*数组元素的巨细。
结论:数组的巨细是各维数的乘积*数组元素的巨细。
这里有一个陷阱:
int *d = new int[10];
cout<<sizeof(d)<<endl; // 4
d是我们常说的动态数组,可是他实质上照旧一个指针,所以sizeof(d)的值是4。
再思量下面的问题:
double* (*a)[3][6];
cout<<sizeof(a)<<endl; // 4
cout<<sizeof(*a)<<endl; // 72
cout<<sizeof(**a)<<endl; // 24
cout<<sizeof(***a)<<endl; // 4
cout<<sizeof(****a)<<endl; // 8
a是一个很奇怪的界说,他暗示一个指向 double*[3][6]范例数组的指针。既然是指针,所以sizeof(a)就是4。
既然a是执行double*[3][6]范例的指针,*a就暗示一个double*[3][6]的多维数组范例,因此sizeof(*a)=3*6*sizeof(double*)=72。同样的,**a暗示一个double*[6]范例的数组,所以sizeof(**a)=6*sizeof(double*)=24。***a就暗示个中的一个元素,也就是double*了,所以sizeof(***a)=4。至于****a,就是一个double了,所以sizeof(****a)=sizeof(double)=8。
6、向函数通报数组的问题
思量下面的问题:
#include <iostream>
using namespace std;
int Sum(int i[])
{
int sumofi = 0;
for (int j = 0; j < sizeof(i)/sizeof(int); j++) //实际上,sizeof(i) = 4
{
sumofi += i[j];
}
return sumofi;
}
int main()
{
int allAges[6] = {21, 22, 22, 19, 34, 12};
cout<<Sum(allAges)<<endl;
system("pause");
return 0;
}
Sum的本意是用sizeof获得数组的巨细,然后求和。可是实际上,传入自函数Sum的,只是一个int 范例的指针,所以sizeof(i)=4,而不是24,所以会发生错误的功效。办理这个问题的要领使是用指针可能引用。
利用指针的环境:
int Sum(int (*i)[6])
{
int sumofi = 0;
for (int j = 0; j < sizeof(*i)/sizeof(int); j++) //sizeof(*i) = 24
{
sumofi += (*i)[j];
}
return sumofi;
}
int main()
{
int allAges[] = {21, 22, 22, 19, 34, 12};
cout<<Sum(&allAges)<<endl;
system("pause");
return 0;
}
在这个Sum里,i是一个指向i[6]范例的指针,留意,这里不能用int Sum(int (*i)[])声明函数,而是必需指明要传入的数组的巨细,否则sizeof(*i)无法计较。可是在这种环境下,再通过sizeof来计较数组巨细已经没有意义了,因为此时巨细是指定为6的。
利用引用的环境和指针相似:
int Sum(int (&i)[6])
{
int sumofi = 0;
for (int j = 0; j < sizeof(i)/sizeof(int); j++)
{
sumofi += i[j];
}
return sumofi;
}
int main()
{
int allAges[] = {21, 22, 22, 19, 34, 12};
cout<<Sum(allAges)<<endl;
system("pause");
return 0;
}
这种环境下sizeof的计较同样无意义,所以用数组做参数,并且需要遍历的时候,函数应该有一个参数来说明数组的巨细,而数组的巨细在数组界说的浸染域内通过sizeof求值。因此上面的函数正确形式应该是:
#include <iostream>
using namespace std;
int Sum(int *i, unsigned int n)
{
int sumofi = 0;
for (int j = 0; j < n; j++)
{
sumofi += i[j];
}
return sumofi;
}
int main()
{
int allAges[] = {21, 22, 22, 19, 34, 12};
cout<<Sum(i, sizeof(allAges)/sizeof(int))<<endl;
system("pause");
return 0;
}
7、字符串的sizeof和strlen
思量下面的问题:
#p#分页标题#e#
char a[] = "abcdef";
char b[20] = "abcdef";
string s = "abcdef";
cout<<strlen(a)<<endl; // 6,字符串长度
cout<<sizeof(a)<<endl; // 7,字符串容量
cout<<strlen(b)<<endl; // 6,字符串长度
cout<<strlen(b)<<endl; // 20,字符串容量
cout<<sizeof(s)<<endl; // 12, 这里不代表字符串的长度,而是string类的巨细
cout<<strlen(s)<<endl; // 错误!s不是一个字符指针。
a[1] = '\0';
cout<<strlen(a)<<endl; // 1
cout<<sizeof(a)<<endl; // 7,sizeof是恒定的
strlen是寻找从指定地点开始,到呈现的第一个0之间的字符个数,他是在运行阶段执行的,而sizeof是获得数据的巨细,在这里是获得字符串的容量。所以对同一个工具而言,sizeof的值是恒定的。string是C++范例的字符串,他是一个类,所以sizeof(s)暗示的并不是字符串的长度,而是类string的巨细。strlen(s)基础就是错误的,因为strlen的参数是一个字符指针,假如想用strlen获得s字符串的长度,应该利用sizeof(s.c_str()),因为string的成员函数c_str()返回的是字符串的首地点。实际上,string类提供了本身的成员函数来获得字符串的容量和长度,别离是Capacity()和Length()。string封装了常用了字符串操纵,所以在C++开拓进程中,最好利用string取代C范例的字符串。
我注:关于sizeof(string),仿佛差异的实现返回的功效纷歧样:
DevCPP:4
VS2005:32
8、从union的sizeof问题看cpu的对界
思量下面问题:(默认对齐方法)
union u
{
double a;
int b;
};
union u2
{
char a[13];
int b;
};
union u3
{
char a[13];
char b;
};
cout<<sizeof(u)<<endl; // 8
cout<<sizeof(u2)<<endl; // 16
cout<<sizeof(u3)<<endl; // 13
都知道union的巨细取决于它所有的成员中,占用空间最大的一个成员的巨细。所以对付u来说,巨细就是最大的double范例成员a了,所以sizeof(u)=sizeof(double)=8。可是对付u2和u3,最大的空间都是char[13]范例的数组,为什么u3的巨细是13,而u2是16呢?要害在于u2中的成员int b。由于int范例成员的存在,使u2的对齐方法酿成4,也就是说,u2的巨细必需在4的对界上,所以占用的空间酿成了16(最靠近13的对界)。
结论:复合数据范例,如union,struct,class的对齐方法为成员中对齐方法最大的成员的对齐方法。
顺便提一下CPU对界问题,32的C++回收8位对界来提高运行速度,所以编译器会只管把数据放在它的对界上以提高内存掷中率。对界是可以变动的,利用#pragma pack(x)宏可以改变编译器的对界方法,默认是8。C++固有范例的对界取编译器对界方法与自身巨细中较小的一个。譬喻,指定编译器按2对界,int范例的巨细是4,则int的对界为2和4中较小的2。在默认的对界方法下,因为险些所有的数据范例都不大于默认的对界方法8(除了long double),所以所有的固有范例的对界方法可以认为就是范例自身的巨细。变动一下上面的措施:
#pragma pack(2)
union u2
{
char a[13];
int b;
};
union u3
{
char a[13];
char b;
};
#pragma pack(8)
cout<<sizeof(u2)<<endl; // 14
cout<<sizeof(u3)<<endl; // 13
由于手动变动对界方法为2,所以int的对界也酿成了2,u2的对界取成员中最大的对界,也是2了,所以此时sizeof(u2)=14。
结论:C++固有范例的对界取编译器对界方法与自身巨细中较小的一个。
9、struct的sizeof问题
因为对齐问题使布局体的sizeof变得较量巨大,看下面的例子:(默认对齐方法下)
struct s1
{
char a;
double b;
int c;
char d;
};
struct s2
{
char a;
char b;
int c;
double d;
};
cout<<sizeof(s1)<<endl; // 24
cout<<sizeof(s2)<<endl; // 16
同样是两个char范例,一个int范例,一个double范例,可是因为对界问题,导致他们的巨细差异。计较布局体巨细可以回收元素摆放法,我举例子说明一下:首先,CPU判定布局体的对界,按照上一节的结论,s1和s2的对界都取最大的元素范例,也就是double范例的对界8。然后开始摆放每个元素。
#p#分页标题#e#
对付s1,首先把a放到8的对界,假定是0,此时下一个空闲的地点是1,可是下一个元素d是double范例,要放到8的对界上,离1最靠近的地点是8了,所以d被放在了8,此时下一个空闲地点酿成了16,下一个元素c的对界是4,16可以满意,所以c放在了16,此时下一个空闲地点酿成了20,下一个元素d需要对界1,也正好落在对界上,所以d放在了20,布局体在地点21处竣事。由于s1的巨细需要是8的倍数,所以21-23的空间被保存,s1的巨细酿成了24。
对付s2,首先把a放到8的对界,假定是0,此时下一个空闲地点是1,下一个元素的对界也是1,所以b摆放在1,下一个空闲地点酿成了2;下一个元素c的对界是4,所以取离2最近的地点4摆放c,下一个空闲地点酿成了8,下一个元素d的对界是8,所以d摆放在8,所有元素摆放完毕,布局体在15处竣事,占用总空间为16,正好是8的倍数。
这里有个陷阱,对付布局体中的布局体成员,不要认为它的对齐方法就是他的巨细,看下面的例子:
struct s1
{
char a[8];
};
struct s2
{
double d;
};
struct s3
{
s1 s;
char a;
};
struct s4
{
s2 s;
char a;
};
cout<<sizeof(s1)<<endl; // 8
cout<<sizeof(s2)<<endl; // 8
cout<<sizeof(s3)<<endl; // 9
cout<<sizeof(s4)<<endl; // 16;
s1和s2巨细固然都是8,可是s1的对齐方法是1,s2是8(double),所以在s3和s4中才有这样的差别。
所以,在本身界说布局体的时候,假如空间告急的话,最好思量对齐因素来分列布局体里的元素。
10、不要让double滋扰你的位域
在布局体和类中,可以利用位域来划定某个成员所能占用的空间,所以利用位域能在必然水平上节减布局体占用的空间。不外思量下面的代码:
struct s1
{
int i: 8;
int j: 4;
double b;
int a:3;
};
struct s2
{
int i;
int j;
double b;
int a;
};
struct s3
{
int i;
int j;
int a;
double b;
};
struct s4
{
int i: 8;
int j: 4;
int a:3;
double b;
};
cout<<sizeof(s1)<<endl; // 24
cout<<sizeof(s2)<<endl; // 24
cout<<sizeof(s3)<<endl; // 24
cout<<sizeof(s4)<<endl; // 16
可以看到,有double存在会过问干与到位域(sizeof的算法参考上一节),所以利用位域的的时候,最好把float范例和double范例放在措施的开始可能最后。