参考文章: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 ); // 结果为4
sizeof( pi ); // 结果为4
sizeof( ps ); // 结果为4
sizeof( ppc ); // 结果为4
sizeof( 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,结果为10
int 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整除的地址上,以此类推,两个成员变量之间可能存在空的地址空间。
字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
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; //结果为12
cout<<"sizeof(s2):"<<sizeof(s2)<<endl; //结果为8
cout<<"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因为内存对齐,首地址的偏移量为能够整除其宽度整除的最小值,即4
cout<<"s1.c:"<<(int)&(s1.c)<<endl; //结果为1243960,第三个成员偏移量为8,
cout<<"position s2:"<<(int)&s2<<endl; //结果为1243936,证明栈是向上生下的,即向着内存地址减小的方向增长
cout<<"s2.a:"<<(int)&(s2.a)<<endl; //结果为1243936
cout<<"s2.b:"<<(int)&(s2.b)<<endl; //结果为1243938
cout<<"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
struct S1{
char a[5];
double b;
};
struct S3 {
int c;
S1 s1;
};
S3 s3;
cout<<"position s3:"<<(int)&s3<<endl; //结果为1243792,可以整除8
cout<<"s3.c:"<<(int)&s3.c<<endl; //结果为1243792
cout<<"s3.s1.a:"<<(int)&s3.s1.a<<endl; //结果为1243800
cout<<"s3.s1.b:"<<(int)&s3.s1.b<<endl; //结果为1243008
cout<<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可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。
使用位域的主要目的是压缩存储,其大致规则为:
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
示例1:
{
char f1 : 3;
char f2 : 4;
char f3 : 5;
};
其内存布局为:
---------------------------------
| | | | | | | | | | | | | | | | |
---------------------------------
0 3 7 8 13 16 (byte)
位域类型为char,第1个字节仅能容纳下f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(BF1)的结果为2。
示例2:{
char f1 : 3;
short f2 : 4;
char f3 : 5;
};
由于相邻位域类型不同,在VC6中其sizeof为6,在Dev-C++中为2。
示例3:{
char f1 : 3;
char f2;
char f3 : 5;
};
非位域字段穿插在其中,不会产生压缩,在VC6和Dev-C++中得到的大小均为3。
8、类的sizeof8.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; //结果为8
a.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++结果为8
cout<<sizeof(A4)<<endl; //vs2010结果为12,g++结果为12
对于子类的sizeof,其大小为父类的sizeof,vs2010与g++有不同的排列方式,vs2010中首先将父类作为一个整体,然后后面加上子类的成员对齐后的大小,g++将父类和子类的成员作为一个整体。