细说C/C++关键字sizeof

      sizeof

      sizeof是C/C++中的一个操作符(operator),其作用是返回一个对象或者类型所占的内存字节数。其返回值类型为size_t,在头文件stddef.h中定义。这是一个依赖于编译系统的值,一般定义为

   typedef unsigned int size_t;
          三种使用形式:

   1) sizeof( 对象/变量 );
   2) sizeof( 类型名字 ); 
   3) sizeof  变量名字

        sizeof可以不加括号直接加变量名字,但是不可以后面直接跟类型名字。(从这一点也可以看出来sizeof肯定不会是一个函数)

    sizeof的使用

    1.sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用,如:

int s[ sizeof(int)];//完全可以的,sizeof计算的值在编译的时候就确定了					          

      2.求指针变量的sizeof

       指针是用来存放另外一个对象的地址的,所以它等于计算机内部地址总线的宽度,所以在所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),但是,在64位系统中指针变量的sizeof结果为8。

    int* p = NULL;
    cout << sizeof( p ) << endl;
    cout << sizeof( *p ) << endl;
    int a[ 100 ];
    cout << sizeof( &a ) << endl;
    cout << sizeof( &a[ 0 ] ) << endl;
     上面代码中打印出来的值都是4,指针变量的sizeof值与指针所指的对象没有任何关系,正是由于所有的指针变量所占内存大小相等。
      3.数组的sizeof

    int a[ 100 ];
    cout << sizeof( a ) << endl;//400
        打印出数组中包含的内存大小,100个int

        

void fun( int b[ 100 ] )
{
    cout << sizeof( b ) << endl;
}
int main()
{
    int a[ 100 ];
    cout << sizeof( a ) << endl;
    fun( a );
    return 0;
}
        fun中打印出来的值是4,调用fun()函数的时候,程序会在栈上分配函数参数,分配int型的指针。数组是“传址”的,调用者只需将实参的地址传递过去,所以b是指针类型,打印出来值为4.

        4.结构体的sizeof   

    这里是最容易犯晕的地方,来一步一步的往下看一下。

struct example
{
    char s;
    int data;
};
int main()
{
    cout << sizeof( example ) << endl;
    return 0;
}
    char 占一个自己,int 占4个字节。我们会认为占有5个字节,但是实际上打印 出来是8,原因是为什么呢。先说个名词:字节对齐

    我们看一下内存是如何分配的,对这个结构体example

    

    可以看到char的地址是0xbffff778 ,而data 的地址为0xbffff77c,差距为4个字节。也就是字节对齐了。

    字节对齐:

    定义:

    字节按照一定规则在空间上排列就是字节对齐。 对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
    为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.


    字节对齐的原因:

    为什么需要字节对齐? 这样有助于加快计算机的取数速度,否则就得多花指令周期了。假设上面结构体中的整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次4个字节就可以取出数据。
     现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
    对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。

    为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。这样,上文结构体中char后面需要填充3个字节,用来让后面的int类型可以一次读取内存就可以直接拿到int类型的值,所以整个结构体的sizeof值就增长了。

    如何处理字节对齐


    对于标准数据类型,它的地址只要是它的长度的整数倍就行了。
    对于非标准数据类型,需要遵守的规则是:
    
    数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
 联合 :按其包含的长度最大的数据类型对齐。
 结构体: 结构体中每个数据类型都要对齐:
    1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
    2) 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
    3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
    
    了解了规则,我们可以首先写一个例子,看一下例子:
    
struct example
{
    char s;
    int data;
    char name[10];
};
    打印出来example的大小 ,值是20.参考 结构体规则 1) 2) 3).

    另外一个例子:
    
struct example
{
    char x1;
    short x2;
    int x3;
    char x4[10];
};
    对这个结构体类型进行sizeof得到的值也是20. 由于编译器默认情况下会对这个struct作自然边界对齐,结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3恰好落在其自然边界地址上,在它前面不需要额外的填充字节。成员x4是字符数组在example结构中,数组按照数组内部基本数据类型对齐,第一个对齐了后面的自然也就对齐了。 成员x3要求4字节对界,是该结构所有成员中要求的最大边界单元,因而example结构的自然对界条件为4字节,编译器在成员x4后面填充了2个空字节。整个结构所占据空间为20字节。


   gcc编译器默认是默认4字节对齐的,我们也可以让编译器对结构作1字节对齐,由我们自己来设置。仍然看下面例子:
    
#include <iostream>

using namespace std;
#pragma pack(1)
struct example
{
    char x1;
    short x2;
    int x3;
    char x4[10];
};
int main()
{
    struct example S;
    cout << sizeof( S ) << endl;
    return 0;
}
    这个时候打印出来的值就是17,让编译器对这个结构作1字节对齐,所以大小是17.如果想恢复原先的4字节对齐,则使用下面命令
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐
    
    

    编译器对齐原则

  •    数据类型自身的对齐值:char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
  •     结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
  •    数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。(在存在指定对齐值的情况下),就是加了下面一条命令。#pragma pack (value)
  •    指定对齐值:#pragma pack (value)时的指定对齐值value。

    有了编译器的对齐规则,我们就来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍)。
   看下面两个例子,研究下根据对齐原则,我们应该如何编程。
    
#include <iostream>

using namespace std;

struct example1
{
    char x1;
    int x2;
    short x3;
};
struct example2
{
    int x1;
    char x2;
    short x3;
};
int main()
{
    struct example1 S1;
    struct example2 S2;
    cout << sizeof( S1 ) << endl;
    cout << sizeof( S2 ) << endl;
    return 0;
}
    其实根据上面的原则,我们可以轻松的理解S1 和S2的大小分别是12和8.原因:
    假设S1从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在gcc编译器下该值默认为4。第一个成员变量char x1的自身对齐值是1(char),比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量x2,其自身对齐值为4(int),所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量x3,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是example1内容。再看数据结构example1的自身对齐值为其变量中最大对齐值(这里是int)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体example1所占用。故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构example1的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
    上面两个字节对齐可以用下面的图来间接的描述是如何对齐的。
    图片和例子来源:http://blog.csdn.net/21aspnet/article/details/6729724(大牛的博客),讲解字节对齐非常的好。

    在确定含有复合数据类型的结构体的自身对齐值的时候,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。看下面的例子。
    
#include <iostream>

using namespace std;
#pragma pack()
struct example1
{
    char x1;
    int x2;
    short x3;
};
struct example2
{
    char x1;
    example1 x2;
    short x3;

};
int main()
{
    struct example1 S1;
    struct example2 S2;
    cout << sizeof( S1 ) << endl;
    cout << sizeof( S2 ) << endl;
    return 0;
}
    example2中包含了example1结构体,所以我们在确定example2的 自身对齐值,应该将example1这个结构类型打散,从这些所有的子成员中找最大的类型。也就是int,so example2的自身对齐值也是4,而不是example1的值12. 这里我们可以得出结果是 12和 20.


    联合体的sizeof:

    
结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个联合体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是复合类型,这里,复合类型成员是被作为整体考虑的。
    
struct example1
{
    char x1;
    int x2;
    short x3;
};
union example2
{
    char x1;
    example1 x2;
    short x3;

};
int main()
{
    struct example1 S1;
    union example2 S2;
    cout << sizeof( S1 ) << endl;
    cout << sizeof( S2 ) << endl;
    return 0;
}
    在union中出现struct结构体的时候,这个时候应该把它作为一个整体来考虑。所以结果是12,12.

    与strlen区别

    
    strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。char aa[10];cout<<strlen(aa)<<endl; //结果是不定的 char aa[10]={'\0'}; cout<<strlen(aa)<<endl; //结果为0 char aa[10]="jun"; cout<<strlen(aa)<<endl; //结果为3=
    而sizeof()函数返回的是变量声明后所占的内存数,不是实际长度。
    例如:sizeof(aa) 返回10 int a[10]; sizeof(a) 返回40

    1.sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
    2.sizeof是算符,strlen是函数。
    3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。
    4.数组做sizeof的参数不退化,传递给strlen就退化为指针了。
    5.大部分编译程序在编译的时候就把sizeof计算过了是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
    6.strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。
    7.sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
    8.当适用于一个结构类型时或变量, sizeof 返回实际的大小,当适用于静态的空间数组, sizeof 归还全部数组的尺寸。sizeof 操作符不能返回被动态分派的数组或外部数组的尺寸
    9.数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,
    
    我们经常使用 sizeof 和 strlen 的场合,通常是计算字符串数组的长度
    看了上面的详细解释,发现两者的使用还是有区别的,从这个例子可以看得很清楚:
    1 char str[20]="0123456789";
    int a=strlen(str); //a=10; >>>> strlen 计算字符串的长度,以结束符 0x00 为字符串结束。
    int b=sizeof(str); //而b=20; >>>> sizeof 计算的则是分配的数组str[20] 所占的内存空间的大小,不受里面存储的内容改变。
    上面是对静态数组处理的结果,如果是对指针,结果就不一样了
    char* ss = "0123456789";
    sizeof(ss) 结果 4 ===》ss是指向字符串常量的字符指针,sizeof 获得的是一个指针的之所占的空间,应该是长整型的,所以是4
    sizeof(*ss) 结果 1 ===》*ss是第一个字符其实就是获得了字符串的第一位'0' 所占的内存空间,是char类型的,占了 1 个字节
    strlen(ss)= 10 >>>> 如果要获得这个字符串的长度,则一定要使用 strlen
    sizeof返回对象所占用的字节大小. //正确
    strlen返回字符个数. //正确
    在使用sizeof时,有一个很特别的情况,就是数组名到指针蜕变,
    char Array[3] = {'0'}; sizeof(Array) == 3; char *p = Array; strlen(p) == 1;//sizeof(p)结果为4
    在传递一个数组名到一个函数中时,它会完全退化为一个指针


    文章参考链接:

    



    

   





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值