Not Only Algorithm,不仅仅是算法,关注数学、算法、数据结构、程序员笔试面试以及一切涉及计算机编程之美的内容 。。
你的位置:NoAlGo博客 » 面试 » ,

C++ sizeof的内存计算(1)

内存控制是程序设计过程中非常关键的一环,C/C++中使用sizeof计算数据占用的内存大小是一个常见的手段,但是这个问题涉及到很多基础的编程细节,能够很好地反映一个程序员的基本功,成为了笔试面试常见的问题之一。
这里总结了一些常见的问题,鉴于篇幅问题,分成两部分进行,这里主要介绍比较基础的第一部分。

  1. C++ sizeof的内存计算(1)
  2. C++ sizeof的内存计算(2)

一 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>&nbsp;</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。

上一篇: 下一篇:

我的博客

NoAlGo头像编程这件小事牵扯到太多的知识,很容易知其然而不知其所以然,但真正了不起的程序员对自己程序的每一个字节都了如指掌,要立足基础理论,努力提升自我的专业修养。

站内搜索

最新评论