说明:
sizeof在笔试面试的时候频频地出现,这也是对基础的一个考查。关于sizeof的文章很多,但感觉大家都没有好好总结下,本着“先行先赢”和“为人民服务”的精神,查找引用参考了很多文章,在这里总结一下,有错误或者遗漏的地方还得请高手多多指教,也不要因这这些问题误导别人,希望以后大家在学习的过程中也能节省些时间。
概要
sizeof是C语言的一种单目操作符(但有人也不这么以为,认为它是一种特殊的宏),如C语言的其他操作符++、--等。它并不是函数(这是必须的)。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定,简单的说其作用就是返回一个对象或者类型所占的内存字节数。
sizeof的使用方法
1、用于数据类型(包括自定义类型)
sizeof使用形式:sizeof(type)
数据类型必须用括号括住。如sizeof(int)。
2、用于变量
sizeof使用形式:sizeof(var_name)或sizeof var_name
变量名可以不用括号括住。如sizeof (var_name),sizeof var_name等都是正确形式。带括号的用法更普遍,大多数程序员采用这种形式。
[注意]sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。
如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式。
sizeof深入理解及陷阱分析
数据类型的sizeof
(1)应用
- 固有类型:
32位C++中的基本数据类型,也就char,short int(short),int,long int(long),float,double, long double
大小分别是:1,2,4,4,4,8, 10。
- 自定义类型:
typedef short WORD;
typedef long DWORD;
(2)举例及陷阱
- e.g. 1.1
// 32位机上int长度为4
cout<<sizeof(int)<<endl;// == 操作符返回bool类型,相当于 cout<<sizeof(bool)<<endl;
cout<<sizeof(1==2)<<endl;
在编译阶段已经被翻译为:
cout<<4<<endl;cout<<1<<endl;
- e.g. 1.2
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当成函数,也不要看作一元操作符,把他当成一个特殊的编译预处理。
- e.g. 1.3
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要对谁取值,最好都加上()。
- e.g. 1.4
cout<<sizeof(unsigned int) == sizeof(int)<<endl; // 相等,输出 1
unsigned影响的只是最高位bit的意义,数据长度不会被改变的。
结论:unsigned不能影响sizeof的取值。
- e.g. 1.5
typedef short WORD;typedef long DWORD;cout<<(sizeof(short) == sizeof(WORD))<<endl; // 相等,输出1cout<<(sizeof(long) == sizeof(DWORD))<<endl; // 相等,输出1
结论:自定义类型的sizeof取值等同于它的类型原形。
- e.g. 1.6
cout << sizeof(2 + 3.14) << endl;
3.14的类型为double,2也会被提升成double类型,所以等价于 sizeof( double );
所以最后的结果是:8
函数类型的sizeof
sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用,并且返回值不能是void(当然也就是没有返回值)。
C99标准规定,函数、不能确定类型的表达式以及位域(bit-field)成员不能被计算sizeof值。
- e.g. 2.1
int f1(){return 0;};double f2(){return 0.0;}void f3(){}
cout<<sizeof(f1())<<endl; // f1()返回值为int,因此被认为是intcout<<sizeof(f2())<<endl; // f2()返回值为double,因此被认为是doublecout<<sizeof(f3())<<endl; // 错误!无法对void类型使用sizeofcout<<sizeof(f1)<<endl; // 错误!无法对函数指针使用sizeof
指针类型的sizeof
学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。
- e.g. 3.1
char* pc = "abc";int* pi;
string* ps;
char** ppc = &pc;
void (*pf)();// 函数指针sizeof( pc ); // 结果为4sizeof( pi ); // 结果为4sizeof( ps ); // 结果为4sizeof( ppc ); // 结果为4sizeof( pf );// 结果为4
可以看到,不管是什么类型的指针,大小都是4的,因为指针就是32位的物理地址。
结论:只要是指针,大小就是4。(64位机上要变成8也不一定)。
数组类型的sizeof
数组类型的sizeof常常就和char*, char[],这些东西在一起就会误导人,这个到最后再讨论,先从例子看起。
- e.g. 4.1
char a[] = "abcdef";int b[20] = {3, 4};
char c[2][3] = {"aa", "bb"};cout<<sizeof(a)<<endl; // 7cout<<sizeof(b)<<endl; // 80cout<<sizeof(c)<<endl; // 6
数组a的大小在定义时未指定,编译时给它分配的空间是按照初始化的值确定的,也就是7,注意:在最后还有一个“\0”也算一位。所以是6+1=7。
本人在VS2005上得到b的结果是80,可以看出,数组的大小就是他在编译时被分配的空间,也就是各维数的乘积*数组元素的大小。
c是多维数组,占用的空间大小是各维数的乘积,也就是6。
结论:数组的大小是各维数的乘积*数组元素的大小。
- e.g. 4.2
int *d = new int[10];cout<<sizeof(d)<<endl; // 4
d是我们常说的动态数组,但是他实质上还是一个指针,所以sizeof(d)的值是4。
- e.g. 4.3
double* (*a)[3][6];
cout<<sizeof(a)<<endl; // 4cout<<sizeof(*a)<<endl; // 72cout<<sizeof(**a)<<endl; // 24cout<<sizeof(***a)<<endl; // 4cout<<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。
向函数传递数组的sizeof
首先,我们要明确数组作为参数被传给函数时传的是指针而不是数组,传递的是数组的首地址。也可以说是它退化成了一个指针。
- e.g. 5.1
#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;
}
结构体的sizeof
结构体的sizeof相对还是麻烦一些,写了一篇专门的文章仅供参考:
链接:http://pppboy.blog.163.com/blog/static/30203796201082494026399/
类的sizeof
[提示]这个可以看看孙鑫的那个《深入浅出MFC》的第一章的那个C++基础部分,就有这个类的大小的一个例子很不错。
先从例子看起吧。
关于虚继承也有个总结:
http://pppboy.blog.163.com/blog/static/30203796201041005953744/
- e.g. 7.1
//空类,相当于一个空结构体
class A
{};//和结构体相同
class B
{int a;
double b;
};//有一个非虚函数
class C
{int a;
double b;
void f(){}
};//有1个虚函数
class D
{int a;
double b;
virtual void vf(){}};//有2个虚函数
class E
{int a;
double b;
virtual void vf(){}virtual void vf2(){}};cout << "A: " << sizeof(A) << endl;cout << "B: " << sizeof(B) << endl;cout << "C: " << sizeof(C) << endl;cout << "D: " << sizeof(D) << endl;cout << "E: " << sizeof(E) << endl;
在8字节对齐的情况下结果为:
A: 1B: 16C: 16D: 24E: 24请按任意键继续. . .
可见:
(1)类的大小和结构体大小一样
(2)非虚拟成员函数不占大小
(3)如果有虚拟函数,则会多占用4个字节(虚拟函数表)
- e.g. 7.2
class A
{int a;
double b;
void f(){}
virtual void vf(){}};class B :public A{};class C : public virtual A{};class D
{char a;
};class E
{char a;
virtual void vfd(){}};class F1 : public A, public D{};class F2 : public A, public E{};class G1 : public virtual A, public virtual D{};class G2 : public virtual A, public virtual E{};cout << "A: " << sizeof(A) << endl;cout << "B: " << sizeof(B) << endl;cout << "C: " << sizeof(C) << endl;cout << "D: " << sizeof(D) << endl;cout << "E: " << sizeof(E) << endl;cout << "F1: " << sizeof(F1) << endl;cout << "F2: " << sizeof(F2) << endl;cout << "G1: " << sizeof(G1) << endl;cout << "G2: " << sizeof(G2) << endl;
在8字节对齐的情况下,结果为:
A: 24B: 24C: 32D: 1E: 8F1: 32F2: 32G1: 33G2: 40请按任意键继续. . .
结论:
(1)在继承的时候,单一继承和多重继承的大小一样,也就相当于子类有一个自己的虚函数表,这个虚函数表是自己重新定义的,相当于它把父亲的虚函数表改成自己的,并且只有一个。
(2)在有虚继承时,涉及虚指针的问题,也就是说,他会用父类的虚函数表,如果父类没有,那么他也就没有这个父类的虚函数表,相当于他不具有自己独立的虚函数表,而是用它父亲的,有几个父亲有表,那么它都可以随时用它父亲的,也算是他自己的大小。
- e.g. 7.3
父类指针指向子类的问题
class Base
{int a;
virtual void vf(){};};class Derived : public Base{char a;
};Base* pBase = new Derived;
Derived* pDerived = new Derived;
cout << "Base: " << sizeof(Base) << endl;//8cout << "Derived: " << sizeof(Derived) << endl;//12cout << "pBase: " << sizeof(pBase) << endl;//指针大小都为4cout << "*pBase: " << sizeof(*pBase) << endl;//8cout << "pDerived: " << sizeof(pDerived) << endl;//指针大小都为4cout << "*pDerived:" << sizeof(*pDerived) << endl;//12
//在8字节对齐时的结果为:
Base: 8Derived: 12pBase: 4*pBase: 8pDerived: 4*pDerived:12请按任意键继续. . .
结论:sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸。它返回的只是类型的大小(可以这么理解)
字符串的sizeof和strlen
这个在实际的应用中是最容易忽悠人的,应该好好滴留意一下。
- e.g. 8.1
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类型的字符串。
下面引用VCbase里的内容再探讨一下。
(1)sizeof是算符,strlen是函数。
(2)sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。sizeof还可以用函数做参数
short f();
printf("%d\n", sizeof(f()));
(3)数组做sizeof的参数不退化,传递给strlen就退化为指针了。
char var[10];
int test(char var[]){return sizeof(var);}
var[]等价于*var,已经退化成一个指针,所以大小是4.
(4)大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
char str[20]="0123456789";int a=strlen(str); //a=10;int b=sizeof(str); //而b=20;
(5)strlen的结果要在运行的时候才能计算出来,时用来计算字符串的长度,不是类型占内存的大小。
(6)sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
(7)当适用了于一个结构类型时或变量, sizeof 返回实际的大小, 当适用一静态地空间数组, sizeof 归还全部数组的尺寸。 sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸
(8)数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如:
fun(char [8])
fun(char [])
都等价于 fun(char *) 在C++里传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小如果想在函数内知道数组的大小, 需要这样做:进入函数后用memcpy拷贝出来,长度由另一个形参传进去.
//看来这个《程序员面试宝典》也是抄这段文字了。。。fun(unsiged char *p1, int len){unsigned char* buf = new unsigned char[len+1]memcpy(buf, p1, len);}
和我一起做题目
容易迷糊的几个问题
- e.g. 1.1
char* ss = "0123456789";
sizeof(ss) 结果 4 ss是指向字符串常量的字符指针
sizeof(*ss) 结果 1 *ss是第一个字符
- e.g. 1.2
char ss[] = "0123456789";
sizeof(ss) 结果 11 ss是数组,计算到\0位置,因此是10+1
sizeof(*ss) 结果 1 *ss是第一个字符
- e.g. 1.3
char ss[100] = "0123456789";
sizeof(ss) 结果是100, ss表示在内存中的大小 100×1
strlen(ss) 结果是10,strlen是个函数内部实现是用一个循环计算到\0为止之前
- e.g. 1.4
int ss[100] = "0123456789";
sizeof(ss) 结果 400 ,ss表示再内存中的大小 100×4
strlen(ss) 错误 ,strlen的参数只能是char* 且必须是以''\0''结尾的
- e.g. 1.5
char q[]="abc";char p[]="a\n";sizeof(q),sizeof(p),strlen(q),strlen(p);
结果是 4 3 3 2
这个容易迷糊,好好看清楚。。注意sizeof(p)是“\n”算一位,而strlen(p)在“\n”的时候就已经结束
e.g. 1.6
char szPath[MAX_PATH]
如果在函数内这样定义,那么sizeof(szPath)将会是MAX_PATH,但是将szPath作为虚参声明时(void fun(char szPath[MAX_PATH])),sizeof(szPath)却会是4(指针大小)
还是退化成指针的问题
网上找到的几个牛B点的
- e.g. 2.1
1. sizeof(char) =2. sizeof 'a' =
3. sizeof "a" =4. strlen("a")) =
答案:1,1,2,1(说明:原来作者给的答案是1,4,2,1。但第二个应该是1而不是4)
- e.g. 2.2
short (*ptr[100])[200];
1. sizeof(ptr) =
2. sizeof(ptr[0]) =
3. sizeof(*ptr[0]) =
4. sizeof((*ptr[0])[0])) =
答案:400,4,400,2
这里我们定义了一个100个指针数组,每个指针均指向有200个元素的数组,其内存占用为200*sizeof(short)字节。那么这100个数组指针的大小sizeof(ptr)为100*sizeof(short*)。接着,指针数组的第一个指针ptr[0]指向第一个数组,所以这个指针ptr[0]的大小实际上就是一个普通指针的大小,即sizeof(short*)。*ptr[0]指向第一个数组的起始地址,所以sizeof(*ptr[0])实际上求的是第一个组的内存大小200*sizeof(short)。(*ptr[0])[0])是第一个数组的第一个元素,因为是short型,所以这个元素的大小sizeof((*ptr[0])[0]))等价于sizeof(short)。
e.g. 2.3
#include <stdio.h>#pragma pack(push)#pragma pack(2)typedef struct _fruit{char apple;
int banana;
short orange;
double watermelon;
unsigned int plum:5;unsigned int peach:28;char* tomato;
struct fruit* next;
} fruit;#pragma pack(4)typedef struct _fruit2{char apple;
int banana;
short orange;
double watermelon;
unsigned int plum:5;unsigned int peach:28;char* tomato;
struct fruit2* next;
} fruit2;#pragma pack(pop)int main(int argc, char *argv[]){printf("fruit=%d,fruit2=%d\n",sizeof(fruit),sizeof(fruit2));}
答案:fruit=30,fruit2=36
听听作者怎么说:
如果你回答错误,那么你对数据结构的对齐还没有吃透。这里#pragma pack(2)强制设置编译器对齐属性为2,所以第一个数据结构以2对齐,sizeof(fruit)=(sizeof(apple)+1)+sizeof(banana)+sizeof(orange)+sizeof(watermelon)+((plum:5bit+peach:28bit+15bit)/8bit)+sizeof(tomato)+sizeof(next)(注意式子中1 和 15bit 表示补齐内存,使其以2对齐,),既sizeof(fruit)=(sizeof(char)+1)+sizeof(int)+sizeof(short)+sizeof(double)+sizeof(char*)+sizeof(struct fruit*)。第一个数据结构声明完了之后,又使用#pragma pack(4)强制设置编译器对齐属性为4,所以同理,可以得到sizeof(fruit2)=(sizeof(char)+3)+sizeof(int)+(sizeof(short)+2)+sizeof(double)+((5bit+28bit+31bit)/8bit)+sizeof(char*)+sizeof(struct fruit2*)。
注:#pragma pack(push)保存默认对齐,#pragma pack(pop)恢复默认对齐。
(这个没有细细看,太累了,明天再看这个例子)
引用申明
有文章在引用了其它作者的很多文字,所有权都归原作者所有,这里仅供本人总结学习,下面给出参考过或者引用过的链接,谢谢作者。
1.http://edu.codepub.com/2009/1117/17745.php
2.http://dev.yesky.com/143/2563643.shtml
3.http://blog.chinaunix.net/u2/62684/showart_489736.html
4.http://www.vckbase.com/document/viewdoc/?id=1054
5.《程序员面试宝典》第二版 p52-p63
本文出处:http://pppboy.blog.163.com/blog/static/302037962010825105652390/