开始学c语言的时候早早就接触到了sizeof这个关键字,当时就有一个让我非常困惑的问题,为什么sizeof能够测量出数组的长度,但是用sizeof去测量一个指向数组的指针,却只能得到指针值的长度。
就像这样
int a[20];
int *b = a;
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(b));
结果:
80
4
这就很奇怪了,大学老师,包括很多的书上都告诉我们数组名是指向数组的首位置的指针,那为什么我sizeof能测量出他的长度,但是把他的值赋值给另外一个指针就不能测量出长度了?我认为数组名就是指向数组首位置的指针这句话可能并不完全正确,或者说它与我理解指针有些不同,他可能是指向数组首位置的指针,但是这个指针的类型,肯定与我们int *b这种指针类型有所不同。
题外话:
数组名是常量地址值,指针是变量指针。
关于sizeof的两个精巧的宏实现,其他资料中查找到的,从中我们能知道sizeof为什么能测量出一个数组的长度
//针对T为一个类型名的情况
#defne _sizeof(T) ( (size_t)((T*)0 + 1))
//针对T为一个变量或者数组名的情况
#define array_sizeof(T) ( (size_t)(&T+1) - (size_t)(&T) )
我们自己去使用这两个宏,确实可以替换掉sizeof,这是为什么呢?我们来分析一下上面的两个宏。
size_t这里不做解释,可以去查询其他资料,这里可以简单的理解为int类型
第一个宏:
转换为我们传入的类型就能+1就能直接得到结果了,这是为什么?
先给出结论,待会在进行解释,这就是因为指针步长的原因。指针步长导致了+1之后,指针的移动距离,就能得出该类型变量所占空间。
第二个宏:
int a[5];
printf("%d\n", a);
printf("%d\n", &a);
结果
1245036
1245036
由运行结果可知,数组名a和&a得内存地址相同。我们由数组和指针的关系知道,a代表这个数字,它相当于一个指针,指向第一个元素(&a[0]),即指向数组的首地址。数组中的其他元素可以通过a的位移得到,此时的步长是以数组中单个的元素类型为单位的。所以有a+1为1245040,即数组中a[1]的地址是1245040(在首地址1245036基础上加int的字节数4得到的)。
&a取的是整个数组元素的地址。虽然&a和a得内存地址相同,但它们的意义不相同,它是代表整个数组的,它的步长单位是整个数组的字节长度(这里是4*5=20),所以&a+1得内存地址为1245056。
a的类型是int[5] 数组,一种特殊的变量类型
&a的类型是int(*)[5] 指针——指向int[5]数组的指针
&a[0]的类型是int* 指针——指向int类型的指针
我们都知道指针是有类型的,你对一个什么样的变量取地址,取出来的指针就是一个什么类型的。这一点其实很好理解,虽然说指针是一个地址,按照我们常规的实现来说,地址就是一个point,那为什么指针还需要类型?他的类型不应该是内存的大小吗?确实很容易陷入这样一个误区。我们换一个角度思考,我们存在&符号,同时也存在*符号。我们知道*符号能取出指针指向的值,所以指针必须要是有类型的啊。不然*符号怎么能知道要去取几个字节的数据。正是因为指针有不同的类型,所以不同的指针+1能,其地址的增长值才会不同,这就被称之为指针步长。
、
有以上可以得出,如果我们传入的是一个int* a指针类型,他虽然指向数组的首位置,但是他的指针类型是int,所以sizeof结果为4,这也就能得出为什么我们使用malloc动态分配的数组,不能使用sizeof去测量出数组长度了。而数组名这种指针就很特殊了,他的指针步长是由分配空间的长度所决定的,所以就能得出正确的结果。
还有一个问题,指针步长,程序是怎么知道的呢?
在运行时,所有的指针原则上一律平等。
而对指针进行+1操作时,到底加几的问题,是编译时决定的。
指针不需要记住自己的步长,编译器会将指针的类型映射到的汇编语言的数据类型,比如32位的机器,一个C语言里的int 型(4字节),对应到汇编语言里的就是双字(DD)define doubleword(一个word就是2个字节)。所以指针移动一个步长,到汇编层,也能知道具体是移多少。汇编层会去处理这个步长。结构体步长 或是 数组步长 的移动也是类似的道理。
所以说sizeof其实在编译时期,就能得出结果了,编译器还真是干了不少事。
这就又出现了一个新的问题,C99的变长数组
int n;
scanf("%d",&n);
int a[n];
printf("%d\n",sizeof(a));
居然也能得出正确的结果!!!!说好的编译期呢?
这个变长数组的特性必定把数组的分配引入了运行时期,附带的,sizeof这个原来的编译期操作也不得不被带入到运行时期。那么,对于动态数组的实现,编译器必定要生成一些代码来对其进行runtime的支持,至于怎么实现的,抱歉我不知道!!但是有一点肯定可以清楚,那就是性能上,肯定会有影响。