文章来自NoAlGo博客原创:C++ sizeof的内存计算(1)
内存控制是程序设计过程中非常关键的一环,C/C++中使用sizeof计算数据占用的内存大小是一个常见的手段,但是这个问题涉及到很多基础的编程细节,能够很好地反映一个程序员的基本功,成为了笔试面试常见的问题之一。
这里总结了一些常见的问题,鉴于篇幅问题,分成两部分进行,这里主要介绍比较基础的第一部分。
一 sizeof定义
在C++中sizeof的使用方法看上去像是一个函数,但实际上它在C++中被定义为一个关键字,同时也是一个单目运算符。sizeof运算的操作数可以是类型或者具体的变量,甚至可以是函数,当使用具体变量做操作数时括号可以省略。
sizeof在代码编译期间完成计算,编译器会把得到的结果插入到调用的地方,因此当使用表达式作为操作数时,它会返回表达式的计算类型的大小,但不会对表达式求值。同理,当使用函数调用作为参数时,它会返回函数返回类型的大小,但是不会执行函数体。
sizeof不能求得void类型的长度,但是可以计算void类型指针的大小,所有类型的指针的大小均为4个字节。
具体参考以下代码的具体输出。
void f(){}; int g(){ return 0; } void test(){ int a = 1; printf("sizeof(a)=%d\n", sizeof(a)); //sizeof(a)=4 printf("sizeof a=%d\n", sizeof a); //sizeof a=4 //printf("sizeof(int)=%d\n", sizeof int);//错误,类型要加括号 printf("sizeof(int)=%d\n", sizeof(int)); //sizeof(int)=4 //rintf("sizeof(f)=%d\n", sizeof(f)); //错误,不能对函数名适用 //printf("sizeof(f())=%d\n", sizeof(f()));//错误,不能对void类型适用 printf("sizeof(g())=%d\n", sizeof(g())); //sizeof(g())=4 printf("sizeof(a++)=%d\n", sizeof(a++)); //sizeof(a++)=4 printf("a = %d\n", a); //a = 1 }
二 数组
计算机中存放数组的是一段连续的内存,使用sizeof可以求得数组所占用的内存字节数。但是,sizeof只能求得静态分配的内存的数组的长度, 不能求得动态分配的内存的大小。对于动态分配内存的数组,其实质上是一个指针,进行sizeof运算时会得到该指针的大小,即4。
另外,我们同样可以使用sizeof求得数组中每个元素的大小,于是简单地使用除法可以求得数组的长度。
使用sizeof结合memset函数可以对数组进行初始化,注意memset初始化是以字节为单位进行填充的。如例子中对于数组a的初始化是失败 的,本意是想每个元素初始为5,结果是每个整数的4个字节都被初始化成5了,然后导致每个元素变成16进制的0×05050505(即整数的4个字节都是 5)。
void testArray(){ int a[10]; memset(a, 5, sizeof(a)); //尝试初始化为5(失败) printf("a[0]=%d\n", a[0]); //a[0]=84215045 printf("%d\n", 0x05050505);//84215045,说明确实是逐字节填充 char b[10]; memset(b, 5, sizeof(b)); //尝试初始化为5(成功) printf("b[0]=%d\n", b[0]);//b[0]=5 printf("sizeof(a)=%d\n", sizeof(a)); //sizeof(a)=40 printf("sizeof(a[0])=%d\n", sizeof(a[0])); //sizeof(a[0])=4 printf("length(a)=%d\n", sizeof(a)/sizeof(a[0]));//length(a)=10 int *c = new int[3]; printf("sizeof(c)=%d\n", sizeof(c)); //sizeof(c)=4 }
这里可以先看下面的样例代码。
在数组定义的地方,使用sizeof求得的是数组的实际大小;但作为参数传进函数时,求得却是另外一个值。
原因是,C++数组作为函数参数在参数传递时,函数中的数组会为退化成为指针。数组参数是传地址调用,系统不会在函数调用栈内再给该数组分类一段空 间,而是把原来数组的指针传递进去,于是该变量变成一个单纯的指针。其大小同时变为固定的4个字节,于是不能用 sizeof(a)/sizeof(a[0])求取数组的长度,这也是一般传递数组参数时会把数组的长度一起传递的原因。
讲到这里需要进一步解释下数组名跟数组指针的区别。二者外形神似,均可以对数组内容进行访问,但是二者还是有一定的区别。
在《C和指针》一书第二版的142页中讲到,数组名的值是一个指针常量,是数组首元素的地址,只有在两种场合下,数组名并不是用指针常量来表示,就 是当数组名作为sizeof操作符和单目操作符&的操作数时。sizeof返回整个数组的长度,而不是指向数组的指针的长度。取一个数组名的地址 所产生的是一个指向数组的指针,而不是一个指向某个指针常量的指针。
数组指针是指向该数组首元素地址的一个指针变量,只是一个普通的变量,可以赋为任何的值。
在数组定义的地方使用sizeof可以求得数组的长度,但是数组在作为参数传递进函数时会退化称为数组指针,于是再使用sizeof只能得到指针的大小4。
void foo1(int a[3]){ printf("In foo1: sizeof(a)=%d\n", sizeof(a)); //In foo1: sizeof(a)=4 } void foo2(int *a){ printf("In foo2: sizeof(a)=%d\n", sizeof(a)); //In foo2: sizeof(a)=4 } void test(){ int a[3] = {1, 2, 3}; printf("In test: sizeof(a)=%d\n", sizeof(a)); //In test: sizeof(a)=12 foo1(a); foo2(a); }
另外,还有一个跟数组指针类似的概念,这里顺带讲解一下。指针数组,表示一个数组,它的内容是一个个的指针。数组指针,表示一个指向数组的指针。参看如下代码比较二者定义方式的区别及其具体的使用方法。
这里又涉及到刚刚讲到的数组名与数组名取地址的区别。一般情况下数组名的值是一个指针常量,是数组首元素的地址。对于一维整型数组,其为整型变量的 指针;对于二维整型数组,其为一维数组的指针。而数组名取地址所产生的是一个指向数组的指针。具体参考如下代码注释中标注的语法正确性。
void test(){ int arr1[4]; int arr2[4][4]; int *(a[4]); //定义指针数组,括号可以省略 a[0] = &arr1[0]; *a[0] = 1; printf("arr1[0]=%d\n", *a[0]); //arr1[0]=1 int (*b)[4];//定义数组指针,为长度4的一维int型数组的指针 //b = arr1; //错误,arr1是int型变量的指针 b = &arr1; //正确,&arr1是长度4一维int型数组的指针 b = arr2; //正确,arr2是长度4一维int数组的指针 //b = &arr2;//错误,&arr2是4x4二维int型数组的指针 b = &arr1; (*b)[0] = 2; //小括号不可省略,因为中括号优先级>星号 printf("arr1[0]=%d\n", (*b)[0]);//arr1[0]=2 }
另外,sizeof不能对不完整的数组求长度。如下代码所示,sizeof(A)试图求数组A的大小,但这里的声明extern int A[]只是告诉编译器A是一个整型数组,并没有明确指出其中包含多少个元素,编译器无法确定求得sizeof的值,编译出错。
而由于B数组的大小已经明确给出了,所以编译时编译器可以顺利求得它的大小,编译正常。
<p> </p>//File1.cpp int A[5]={1, 2, 3, 4, 5}; int B[5]={5, 4, 3, 2, 1}; //File2.cpp extern int A[]; extern int B[5]; printf("sizeof(A)=%d\n", sizeof(A)); //编译错误 printf("sizeof(B)=%d\n", sizeof(B)); //编译通过
最后用一个经典的题目做为这一部分的结尾:
void test(){ double* (*a)[3][6]; printf("sizeof(a)=%d\n", sizeof(a)); printf("sizeof(*a)=%d\n", sizeof(*a)); printf("sizeof(**a)=%d\n", sizeof(**a)); printf("sizeof(***a)=%d\n", sizeof(***a)); printf("sizeof(****a)=%d\n", sizeof(****a)); //以上结果输出为: //sizeof(a)=4 //sizeof(*a)=72 //sizeof(**a)=24 //sizeof(***a)=4 //sizeof(****a)=8 }
解释如下,由小括号的优先级知,a是指针,那么它指向什么呢?由后面的定义知,它指向一个3×6的二维数组,但是这个二维数组的元素不是一般的 double,而是double*,即double类型的指针。于是,整体定义为,a是一个指向double*[3][6]类型数组的指针。
a是指针,所以sizeof(a)=4。
a是指向double*[3][6]类型的指针,*a就表示一个double*[3][6]的二维数组,所以sizeof(*a)=3*6*sizeof(double*)=72。
*a表示一个double*[3][6]的二维数组,**a就表示一个double*[6]的一维数组,所以sizeof(**a)=6*sizeof(double*)=24。
**a表示一个double*[6]的一维数组,***a就表示其中的元素double*,所以sizeof(***a)=4。
***a表示其中的元素double*,****a就表示一个double,所以sizeof(****a)=sizeof(double)=8。