sizeof 解惑笔记

来源:互联网 发布:slack是什么软件 编辑:程序博客网 时间:2024/06/11 05:59

参考文章:http://blog.csdn.net/freefalcon/article/details/54839


1、基本属性

sizeof是c/c++中的一个操作符,其作用是返回一个对象或者一个类占用的内存字节(B)数。返回值类型为 size_t ,这是一个依赖于编译系统的值,一般定义为

typedef unsigned int size_t;

一般写为sizeof(A),A为对象或者类型。即

int n;sizeof(n);sizeof(int);

这两种写法是一样的,都是返回对象类型的占用内存字节数。

sizeof的计算发生在编译时刻,因此可以把它当发作敞亮表达式来使用。

char ary[ sizeof( int ) * 10 ]; // ok

最新的C99标准规定sizeof也可以在运行时刻进行计算,如下面的程序在Dev-C++中可以正确执行:

int n;n = 10; // n动态赋值char ary[n]; // C99也支持数组的动态定义printf("%d/n", sizeof(ary)); // ok. 输出10

但在没有完全实现C99标准的编译器中就行不通了,上面的代码在VC6中就通不过编译。所以我们最好还是认为sizeof是在编译期执行的,这样不会带来错误,让程序的可移植性强些。

一般情况下,short、long、int这类的内置数据类型,是与系统相关的,在不同的系统中可能不同,这需要引起我们的注意,尽量不要在这方面给自己造成困难。例如一般在32位系统中,sizeof(int)的值为4,sizeof(double)的值为8。

2、指针变量的sizeof

指针记录了一个对象的地址,那么它等于计算机内部的总线的宽度,所以在32位计算机当中,指针变量的sizeof返回值为4(字节B),64位系统返回8。

char* pc = "abc";int* pi;string* ps;char** ppc = &pc;void (*pf)(); // 函数指针sizeof( pc ); // 结果为4sizeof( pi ); // 结果为4sizeof( ps ); // 结果为4sizeof( ppc ); // 结果为4sizeof( pf ); // 结果为4
3、数组的sizeof

数组的sizeof返回数组所占用的内存字节数,例如:

char a1[] = "abcd";char a2[2];sizeof(a1);//结果为5,末尾还有一个结束字符 '\0'sizeof(a2);//结果为2
求数组的元素个数

int a[10];int c1 = sizeof(a) / sizeof(int);// 30/3,结果为10int c2 = sizeof(a) / sizeof(a[0]);结果也为10
数组作为参数

void foo1(int a1[3]){int c1 = sizeof(a1);//返回结果为4}void foo2(int a2[]){int c2 = sizeof(a2);//返货结果为4}
这里a1、a2已经不再是数组,而是已经蜕变为指针,相当于int *a3

4、联合体的sizeof

联合体是重叠式的,各个成员共享一段内存,所以整个联合体的sizeof也是sizeof中元素的最大值。联合体的的成员也可以使联合体或者结构体,这里的联合体或者结构体作为整体考虑。

union UU1{long c1;int c2;U3 c3;};int n = sizeof(UU1);//返回sizeof(c3)、sizeof(c1)和sizeof(c3)中最大的那个
5、结构体的sizeof

结构体中最重要的概念就是字节对齐,对齐的主要目的在于加快计算机取数的速度,编译器默认会对结构体进行对齐处理(实际上其他地方的数据也是如此),试想如果让字符宽度为2的基本数据都位于能被2整除的地址上,字符宽度为4的位于能被4整除的地址上,以此类推,两个成员变量之间可能存在空的地址空间。

字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。

struct S1{char a;int b;short c;};struct S2{short a;char b;int c;};S1 s1;S2 s2;cout<<"sizeof(s1):"<<sizeof(s1)<<endl; //结果为12cout<<"sizeof(s2):"<<sizeof(s2)<<endl; //结果为8cout<<"position s1:"<<(int)&s1<<endl; //结果为1243952,1243952/4 = 310988,首地址能够被最宽成员整除cout<<"s1.a:"<<(int)&(s1.a)<<endl; //结果为1243952,第一个成员的首地址与该结构体的首地址相同cout<<"s1.b:"<<(int)&(s1.b)<<endl; //结果为1243956,第二个成员宽度为4因为内存对齐,首地址的偏移量为能够整除其宽度整除的最小值,即4cout<<"s1.c:"<<(int)&(s1.c)<<endl; //结果为1243960,第三个成员偏移量为8,cout<<"position s2:"<<(int)&s2<<endl; //结果为1243936,证明栈是向上生下的,即向着内存地址减小的方向增长cout<<"s2.a:"<<(int)&(s2.a)<<endl; //结果为1243936cout<<"s2.b:"<<(int)&(s2.b)<<endl; //结果为1243938cout<<"s2.c:"<<(int)&(s2.c)<<endl; //结果为1243940
不同的电脑运行得出的首地址可能不同,但偏移量是形同的。这里两个结构体的内存结构可表示为(N表示成员变量所站存储位置,F代表填充位置)

s1  NFFF NNNN NNFF ,第一个N为a,为了b的内存对齐,后面三个F为填充,再四个N为b,再两个N为c,最后两个F的填充为结构体整体宽度填充

s2 NNNFNNNN  ,s2明显比s1节约内存空间

char m[100] = {0};s1.a = 0xFF;s1.b = 0xFFFFFFFF;s1.c = 0xEEEE;memcpy(m,&s1,sizeof(s1));//查看m,显示为FF CC CC CC  FF FF FF FF FF FF FF FF EE EE CC CC

所以在书写结构体时,要注意结构体内成员的顺序问题。

对于上面的准则,有几点需要说明:

1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢?因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。
结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:

#define offsetof(s,m) (size_t)&(((s *)0)->m)

例如,想要获得S2中c的偏移量,方法为

size_t pos = offsetof(S1, b); // pos等于4

2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。(引用作者原话,但觉得他说的似乎有点问题
这句话应该这样理解当结构体当中存在结构体成员时,在进行对齐操作的时候应该按照三原则中的第一条进行,可以将整个的对齐过程理解为一个递归的过程,在遇到结构体成员时,重新开始匹配第一条原则,即:首地址偏移应该能整除结构体成员中的最宽的基本类型成员。
还是来看一个例子:
struct S1{char a[5];double b;};struct S3 {int c;S1 s1;};S3 s3;cout<<"position s3:"<<(int)&s3<<endl; //结果为1243792,可以整除8cout<<"s3.c:"<<(int)&s3.c<<endl; //结果为1243792cout<<"s3.s1.a:"<<(int)&s3.s1.a<<endl; //结果为1243800cout<<"s3.s1.b:"<<(int)&s3.s1.b<<endl; //结果为1243008cout<<sizeof(S3)<<endl; //结果为24


整个过程可以这样理解:首先将S1打散看,确认S1中宽度最大的基本类型成员的宽度为8,找到S1的首地址,然后开始填充各个成员变量。

首先第一个变量c占四个字节,然后遇到s1,s1中宽度最大的成员变量的8,那么s1的偏移量能整除8,所以s1的偏移量为8,s1中的第一个成员变量a占5个字节,从s1的偏移位置开始填充5个字节,再然后发现b占8个字节,s1中a已经占用了5个字节,b相对s1的偏移开始的偏移量可以整除8,所以b的偏移量为8,s1中最大的成员宽度为8,现在s1已经占用了16个字节了,可以整除8,所以后面不必填充,sizeof(s1)为16,s3不再有成员变量,现在s3占用宽度为16+8=24,因为s3中宽度最大的成员宽度为8,24可以整除8,后面不必填充。所以sizeof(s3)为24。

6、空结构体的sizeof

struct S1 {};cout<<"S1:"<<sizeof(S1)<<endl; //结果为1


7、含位域结构体的sizeof(因为觉得上面引用的作者已经讲的非常好了,所以就直接copy了

前面已经说过,位域成员不能单独被取sizeof值,我们这里要讨论的是含有位域的结构体的sizeof,只是考虑到其特殊性而将其专门列了出来。

C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

使用位域的主要目的是压缩存储,其大致规则为:

1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

示例1:

struct BF1
{
    char f1 : 3;
    char f2 : 4;
    char f3 : 5;
};

其内存布局为:

| f1  |  f2   | |  f3     |     |
---------------------------------
| | | | | | | | | | | | | | | | |
---------------------------------
0     3       7 8         13    16 (byte)

位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(BF1)的结果为2。

示例2:
struct BF2
{
    char f1 : 3;
    short f2 : 4;
    char f3 : 5;
};

由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。

示例3:
struct BF3
{
    char f1 : 3;
    char f2;
    char f3 : 5;
};

非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。

8、类的sizeof

8.1 不考虑继承的情况下

class A{};cout<<sizeof(A)<<endl;//结果为1
空类的sizeof为1

#include <iostream>using namespace std;class A{public:A():d(1){}void changeC(int n){c= n;}int returnC(){return c;}/*void changeD(int n){        d = n;}*/ //错误,const值不能够被改变private:char a;static int c;const int d;};int A::c = 9;int main(){A a;cout<<sizeof(a)<<endl; //结果为8a.changeC(10);A b;cout<<b.returnC()<<endl; //结果为10,说明static变量被初始化一次,存储在静态区域,上一个实例类的改变会影响下一个实例类return 0;}
类的sizeof只与类中的非static成员变量有关,并且存在内存对齐问题。

static变量存储在静态存储区域,整个类的初始化阶段只会被初始化一次,即使一个类被实例化n次,static变量也不会再被初始化。
const变量存储在栈中,不能被改变。

class A{public:void foo1(){}virtual void foo2(){}private:char a;int b;};cout<<sizeof(A)<<endl; //结果为12
当类中有虚函数时,无论有几个虚函数,sizeof类的大小都等于sizeof(数据成员)+ sizeof(虚函数表指针,4)
8.2 考虑单继承

class A2{public :int a;private:char b;};class A3 : public A2{public:char c;short a;};class A4 : public A2{public:short b;char a;};cout<<sizeof(A3)<<endl; //vs2010结果为12,g++结果为8cout<<sizeof(A4)<<endl; //vs2010结果为12,g++结果为12
对于子类的sizeof,其大小为父类的sizeof,vs2010与g++有不同的排列方式,vs2010中首先将父类作为一个整体,然后后面加上子类的成员对齐后的大小,g++将父类和子类的成员作为一个整体。




原创粉丝点击