您的位置:首页 > 其它

转载--sizeof的用法总结

2011-04-20 21:37 267 查看
sizeof在笔试面试的时候频频地出现,这也是对基础的一个考查。关于sizeof的文章很多,但感觉大家都没有好好总结下,本着“先行先赢”和“为人民服务”的精神,查找引用参考了很多文章,在这里总结一下,有错误或者遗漏的地方还得请高手多多指教,也不要因这这些问题误导别人,希望以后大家在学习的过程中也能节省些时间。

一、概要

sizeof是C语言的一种单目操作符(但有人也不这么以为,认为它是一种特殊的宏),如C语言的其他操作符++、--等。它并不是函数(这是必须的)。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定,简单的说其作用就是返回一个对象或者类型所占的内存字节数。

二、sizeof的使用方法

[b]1、用于数据类型(包括自定义类型)[/b]sizeof使用形式:sizeof(type)数据类型必须用括号括住。如sizeof(int)。[b]2、用于变量 [/b]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深入理解及陷阱分析

[b]1.数据类型的sizeof [/b](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; // 相等,输出1
cout<<(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[b]2.函数类型的sizeof[/b]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,因此被认为是int
cout<<sizeof(f2())<<endl; // f2()返回值为double,因此被认为是double
cout<<sizeof(f3())<<endl; // 错误!无法对void类型使用sizeof
cout<<sizeof(f1)<<endl;  // 错误!无法对函数指针使用sizeof
// *f2,和f2()等价,因为可以看作object,所以括号不是必要的。被认为是double
cout<<sizeof*f2<<endl;
[b]3.指针类型的sizeof[/b]学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。e.g. 3.1
char* pc = "abc";   
int* pi;   
string* ps;   
char** ppc = &pc;   
void (*pf)();// 函数指针
  
sizeof( pc ); // 结果为4   
sizeof( pi ); // 结果为4   
sizeof( ps ); // 结果为4   
sizeof( ppc ); // 结果为4   
sizeof( pf );// 结果为4
可以看到,不管是什么类型的指针,大小都是4的,因为指针就是32位的物理地址。结论:只要是指针,大小就是4。(64位机上要变成8也不一定)。[b]4.数组类型的sizeof[/b]数组类型的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; // 7
cout<<sizeof(b)<<endl; // 80
cout<<sizeof(c)<<endl; // 6
数组a的大小在定义时未指定,编译时给它分配的空间是按照初始化的值确定的,也就是7,注意:在最后还有一个“/n”也算一位。所以是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;  // 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。[b]5.向函数传递数组的sizeof[/b]首先,我们要明确数组作为参数被传给函数时传的是指针而不是数组,传递的是数组的首地址。也可以说是它退化成了一个指针。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;
}
[b]6.结构体的sizeof[/b]结构体的sizeof相对还是麻烦一些,写了一篇专门的文章仅供参考:链接:http://pppboy.blog.163.com/blog/static/30203796201082494026399/[b]7.类的sizeof[/b][提示]这个可以看看孙鑫的那个《深入浅出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: 1
B: 16
C: 16
D: 24
E: 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(){}
};
    //无论是D继承D,还是F都一样
    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: 24
B: 24
C: 32
D: 1
E: 8
F1: 32
F2: 32
G1: 33
G2: 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;//8
    cout << "Derived:  " << sizeof(Derived) << endl;//12
    cout << "pBase:    " << sizeof(pBase) << endl;//指针大小都为4
    cout << "*pBase:   " << sizeof(*pBase) << endl;//8
    cout << "pDerived: " << sizeof(pDerived) << endl;//指针大小都为4
cout << "*pDerived:" << sizeof(*pDerived) << endl;//12
//在8字节对齐时的结果为:
Base:     8
Derived:  12
pBase:    4
*pBase:   8
pDerived: 4
*pDerived:12
请按任意键继续. . .
结论:sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸。它返回的只是类型的大小(可以这么理解)[b]8.字符串的sizeof和strlen[/b]这个在实际的应用中是最容易忽悠人的,应该好好滴留意一下。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);
}
//看来这个《程序员面试宝典》也是抄这段文字了。。。

四、和我一起做题目

[b]1.容易迷糊的几个问题[/b]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+1sizeof(*ss) 结果 1 *ss是第一个字符e.g. 1.3
char ss[100] = "0123456789";
sizeof(ss) 结果是100, ss表示在内存中的大小 100×1strlen(ss) 结果是10,strlen是个函数内部实现是用一个循环计算到/0为止之前e.g. 1.4
int ss[100] = "0123456789";
sizeof(ss) 结果 400 ,ss表示再内存中的大小 100×4strlen(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]2.网上找到的几个牛B点的[/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)恢复默认对齐。(这个没有细细看,太累了,明天再看这个例子)

五、引用申明

有文章在引用了其它作者的很多文字,所有权都归原作者所有,这里仅供本人总结学习,下面给出参考过或者引用过的链接,谢谢作者。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: