sizeof运算符详细总结

sizeof运算符的系统总结

标签:c/c++

总结来自 http://blog.csdn.net/w57w57w57/article/details/6626840

  • 简介

       sizeof是C/C++中的关键字,它是一个运算符,其作用是取得一个对象(数据类型或者数据对象)的长度(即占用内存的大小,以byte为单位)。其中类型包含基本数据类型(不包括void)、用户自定义类型(结构体、类)、函数类型。数据对象是指用前面提到的类型定义的普通变量和指针变量(包含void指针)。不同类型的数据的大小在不同的平台下有所区别,但是c标准规定所有编译平台都应该保证 sizeof(char) 等于1。关于sizeof的更多概述你可以在msdn总输入sizeof进行查询。
      看了上面这些,或许你看了没有多少感觉。没关系,下面我将详细列出sizeof的诸多特性,这些特性是造成sizeof是一个较刁钻的关键字的原因。

  • 特性

    • 特性0:sizeof是运算符,不是函数

        这个特性是sizeof的最基本特性,后面的很多特性都是受到这个特性的影响,正因为sizeof不是函数,因此我们不把它括号中所要处理的对象叫做参数,可以叫做操作数(这不严谨,但是有助于我记住sizeof是个运算符)。

    • 特性1:sizeof不能求得 void 类型的长度

        是的,你不能用 sizeof(void) ,这将导致编译错误: illegalsizeof operand 。事实上你根本就无法声明void类型的变量,不信你就试试 void a; 这样的语句,编译器同样会报错: illegal use of type 'void'。或许你要问为什么,很好,学东西不能只知其然,还要知其所以然。我们知道声明变量的一个重要作用就是告诉编译器该变量需要多少存储空间。然而,void是“空类型”,什么是空类型呢,可以理解成不知道存储空间大小的类型。既然编译器无法确定void类型的变量的存储大小,那么它自然不让你声明这样的变量。当然了,声明void类型的指针是可以的!这就是特性2的内容。

    • 特性2:sizeof能求得 void * 类型的长度

        在特性1中说过,可以声明void类型的指针,也就是说编译器可以确定void类型的指针所占用的存储空间。事实上确实如此,目前,几乎所有平台上的所有版本的编译器都把指针的大小看做4byte,不信你试试 sizeof(int*); sizeof(void*); sizeof(double*); sizeof(Person*); 等等,它们都等于4!为什么呢?原因很简单:其实指针也是变量,只不过这个变量很特殊,它是存放其他变量的地址的变量。又由于目前32位计算机平台上的程序段的寻址范围都是4GB,寻址的最小单元是byte,4GB等于232Byte,这么多的内存其地址如果编码呢,只需要用32个bit就行了,而32bit = 32/8 = 4byte,也就是说只需要4byte就能存储这些内存的地址了。因此对任何类型的指针变量进行sizeof运算其结果就是4!

    • 特性3:sizeof能求得静态分配内存的数组的长度

         Int a[10]; int n = sizeof(a); 假设sizeof(int)等于4,则n= 10*4=40;特别要注意: char ch[]="abc"; sizeof(ch); 结果为4,注意字符串数组末尾有’\0’!通常我们可以利用sizeof来计算数组中包含的元素个数,其做法是: int n = sizeof(a)/sizeof(a[0]);

      【需要注意】对函数的形参数组使用sizeof的情况。举例来说,假设有如下的函数:

      void fun(int array[10])
      {
          int n = sizeof(array);
      }

        你觉得在fun内,n的值为多少呢?如果你回答40的话,那么我很遗憾的告诉你,并不是。这里n等于4,事实上,不管形参是int的型数组,还是float型数组,或者其他任何用户自定义类型的数组,也不管数组包含多少个元素,这里的n都是4!为什么呢?原因是在函数参数传递时,数组被转化成指针了,或许你要问为什么要转化成指针,简单来说:假如直接传递整个数组的话,那么必然涉及到数组元素的拷贝(实参到形参的拷贝),当数组非常大时,这会导致函数执行效率极低!而只传递数组的地址(即指针)那么只需要拷贝4byte。

    • 特性4:sizeof不能求得动态分配的内存的大小!

        假如有如下语句: int *a = new int[10]; int n = sizeof(a); 那么n的值是多少呢?是40吗?答案是否定的!其实n等于4,因为a是指针,在特性2中讲过:在32位平台下,所有指针的大小都是4byte!切记,这里的a与特性3中的a并不一样(特性3中a是数组名,此处的a是指针)!很多人都认为数组名就是指针,其实不然,二者有很多区别的。通过特性3和特性4,我们看到了数组和指针有着千丝万缕的关系,这些关系也是导致程序潜在错误的一大因素。

        特性3指出sizeof能求静态分配的数组的大小,而特性4说明sizeof不能求的动态分配的内存的大小。那么问题来了,sizeof是在什么时候进行求值运算的?有人认为sizeof是编译时进行求值的,并给出理由:语句 int array[sizeof(int)*10]; 能编译通过,而很多书上都说过数组大小是编译时就确定下来的,既然前面的语句能编译通过,所以认为sizeof是编译时进行求值的。经过进一步测试我发现这个结论有些武断!至少是有些不严谨!因为在实现了c99标准的编译器(如DEV C++)中可以定义动态数组,即语句: int num; cin>>num; int arrary[num]; 是对的(注意在vc6.0中是错的)。因此我就在DEV C++中对刚才的array利用语句 int n = sizeof(array); cout<<n<<endl 来求大小,结果编译通过,运行时输入num的值10之后,输出n等于40!在这里很明显num的值是运行时才输入的,因此sizeof不可能在编译时就求得array的大小!这样一来sizeof又变成是运行时求值的了。

        那么问题来了,到底sizeof是编译时求值还是运行时求值呢?答案就是:最开始的c标准规定sizeof只能编译时求值,后来c99又补充规定sizeof可以运行时求值。【注意】但是,即便是在实现了c99标准的DEV C++中仍然不能用sizeof求得动态分配的内存的大小!

    • 特性5:sizeof不能对不完整的数组求长度!

      在阐述该特性之前,我们假设有两个源文件:file1.cpp和file2.cpp
      其中file1.cpp中有如下的定义:

      int arrayA[10] = {1,2,3,4,5,6,7,8,9,10};
      int arrayB[10] = {11,12,13,14,15,16,17,18,19,20};

      file2.cpp包含如下几个语句:

      extern int arrayA[];
      extern int arrayB[10];
      cout<<sizeof(arrayA)<<endl;            //编译出错!!
      cout<<sizeof(arrayB)<<endl;

        在file2.cpp中第三条语句编译出错,而第四条语句正确,并且能输出40!为什么呢?原因就是sizeof(arrayA)试图求不完整数组的大小。这里的不完整的数组是指数组大小没有确定的数组!sizeof运算符的功能就是求某种对象的大小,然而声明: extern int arrayA[] 只是告诉编译器arrayA是一个整型数组,但是并没告诉编译器它包含多少个元素,因此对file2.cpp中的sizeof来说它无法求出arrayA的大小,所以编译器干脆不让你通过编译。

        那为什么sizeof(arrayB)又可以得到arraryB的大小呢?关键就在于在file2.cpp中其声明时使用 extern int arrayB[10] 明确地告诉编译器arrayB是一个包含10个元素的整型数组,因此大小是确定的。

    • 特性6:当表达式作为sizeof的操作数时,它返回表达式的计算结果的类型大小,但是它不对表达式求值!

      为了说明这个问题,我们来看如下的程序语句:

      char ch  = 1;
      int  num = 1;
      int  n1  = sizeof(ch+num);
      int  n2  = sizeof(ch = ch+num);

        假设char占用1byte,int占用4byte,那么执行上面的程序之后,n1,n2,ch的值是多少呢?我相信有不少人会认为n1与n2相等,也有不少人认为ch等于2,事实这些人都错了。事实上n1等于4,n2等于1,ch等于1,为什么呢?

        由于默认类型转换的原因,表达式ch+num的计算结果的类型是int,因此n1的值为4!而表达式 ch=ch+num; 的结果的类型是char,记住虽然在计算ch+num时,结果为int,但是当把结果赋值给ch时又进行了类型转换,因此表达式的最终类型还是char,所以n2等于1。n1,n2的值分别为4和1,其原因正是因为sizeof返回的是表达式计算结果的类型大小,而不是表达式中占用最大内存的变量的类型大小!

        对于 n2=sizeof(ch=ch+num); 乍一看该程序貌似实现了让ch加上num并赋值给ch的功能,事实并非如此!由于sizeof只关心类型大小,所以它自然不应该对表达式求值,否则有画蛇添足之嫌了。正是因为这点,这里告诫各位,尽量不要在sizeof中直接对表达式求大小,以免出现错误,你可以将 sizeof(ch = ch+num); 改写成 ch = ch +num; sizeof(ch); 虽然多了一条语句,看似冗余了,其实好处多多:首先更加清晰明了,其次不会出现ch等于1这样的错误(假设程序的逻辑本身就是要执行ch = ch +num;)。

    • 特性7:sizeof可以对函数调用求大小,并且求得的大小等于返回类型的大小,但是不执行函数体

      假设有如下函数(是一个写得很不好的函数,但是能很好的说明需要阐述的问题):

      int fun(int& num,const int& inc)
      {
          float div = 2.0;
          double ret =0;
          num = num+inc;
          ret = num/div;
          return ret;
      }

      那么语句:

      int a = 3;
      int b = 5;
      cout<<sizeof(fun(a,b))<<endl;
      cout<<a<<endl;

        输出多少呢?不同的人会给出不同的答案,我将对sizeof(fun(a,b))的值和a的值分别进行讨论:

        首先sizeof(fun(a,b))的值:其正确是4,因为用sizeof求函数调用的大小时,它得到的是函数返回类型的大小,而fun(a,b)的返回类型是int,sizeof(int)等于4。很多人把函数的返回类型和返回值的类型弄混淆了,认为sizeof(fun(a,b))的值是8,因为函数返回值是ret,而ret被定义成double,sizeof(doube)等于8。注意,虽然函数返回值类型是double,但是在函数返回时,将该值进行了类型转换(这里的转换不安全)。也有人错误的认为sizeof(fun(a,b))的值是12,它们的理由是:fun内部定义了两个局部变量,一个是float一个是double,而 sizeof(float) + sizeof(doube) = 4+8 = 12 。这样的答案看似很合理,其实他们是错误地认为这里的sizeof是在求函数内部的变量的大小了。这当然是错误的。

        接下来看a的值:其正确答案是3!还记得特性6吗?这里很类似,sizeof的操作对象是函数调用时,它不执行函数体!为此,建议大家不要把函数体放在sizeof后面的括号里,这样容易让人误以为函数执行了,其实它根本没执行。

        既然对函数条用使用sizeof得到的是函数返回类型的大小,那么很自然能得出这样的结论:不能对返回类型为void的函数使用sizeof求其大小!原因请参考特性1。同理,对返回类型是任何类型的指针的函数调用使用sizeof求得的大小都为4,原因请参考特性2。

        最后我们来看看这样的语句: cout<<sizeof(fun); 其答案是多少呢?其实它得不到答案,原因是编译就通不过!最开始,我以为能输出答案4,因为我认为fun是函数名,而我知道函数名就是函数的地址,地址就是指针,于是我认为sizeof(fun)其实就是对一个指针(函数名是指针常量?)求大小,根据特性2,任何指针的大小都是4。可是验证时,编译器根本不让通过!这个是为什么呢?或许是标准有规定吧。暂时没有准确的答案。

    • 特性8:sizeof求得的结构体(及其对象)的大小并不等于各个数据成员对象的大小之和!
      给出相关规则,具体分析见sizeof求解结构体大小的问题

      (1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
      (2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。

        需要提醒的是,在进行设计时,最好仔细安排结构体中各个成员的顺序,因为你已经看到了上面的结构体B与结构体A包含的成员相同,只不过顺序略有差异,最终就导致了B比A多消耗了50%的空间,假如在工程中需要定义该结构体的数组,多消耗的空降将是巨大的。

    • 特性9:sizeof不能用于求结构体的位域成员的大小,但是可以求得包含位域成员的结构体的大小!

        首先解释一下什么是位域:类型的大小都是以字节(byte)为基本单位的,比如sizeof(char)为1byte,sizeof(int)为4byte等。我们知道某个类型的大小确定了该类型所能定义的变量的范围,比如sizeof(char)为1byte,而1byte等于8bit,所以char类型的变量范围是-128-127,或者0-255(unsigned char),总之它只能定义2^8=256个数!然而,要命的是bool类型只取值true和false,按理所只用1bit(即1/8byte)就够了,但事实上sizeof(bool)等于1(使用了一个byte)。因此我们可以认为bool变量浪费了87.5%的存储空间!这在某些存储空间有限的设备(比如嵌入式设备)上是不合适的,为此需要提供一种能对变量的存储空间精打细算的机制,这就是位域。简单来说,在结构体的成员变量后面跟上的一个冒号+一个整数的形式,就代表位域,请看如下的结构体:

      Struct A
      {
          Bool b:1;
          char ch1:4;
          char ch2:4;
      }item;

        其中b,ch1,ch2都是位域成员。该结构体的试图让bool类型的变量b只占用1个bit,让ch1和ch2分别只占用4个bit,以此来达到对内存精打细算的功能(事实上使用位域对内存精打细算有时候能成功,有时候却未必)。另外需要特别注意的是:c语言规定位域只能用于int,signed int或者unsigned int类型,C++又补充了char和long类型!你不能这样使用位域:float:9;这是不能通过编译的。并且位域变量不能在函数或者全局区定义,只能在结构体,自定义类,联合(union)中使用!

        基于上面的结构体,语句sizeof(item.b)和sizeof(item.ch1)等对位域成员求大小的语句均不能通过编译。其原因是:sizeof以byte为单位返回操作数的大小!

        那么,sizeof(A)能否通过编译呢?如何能,其结果又是多少呢?这是两给非常好的问题,事实上我之前没有看到任何关于这方面的论述(可能是我看的资料不足),我正是在看到sizeof(item.b)不能通过编译时想到了这两个问题,然后通过验证得出了后面的结论:对包含位域的结构体是可以使用sizeof求其大小的,但其求值规则比较复杂,不仅涉及到成员对齐,还与具体编译环境有关!在这里你只需要知道可以对包含位域的结构体使用sizeof求其大小就可以了。

  • 364
    点赞
  • 1111
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值