c语言sizeof数组'0',关于sizeof和c语言数组问题

今天考试,考到了sizeof对结构求大小和c语言数组的问题。这两个题让我吃尽苦头,还是自己的功力不够呀,不过事后查了不少资料,也算是学到了一些东西,还是有收获的。

sizeof(struct)涉及到字节对齐的问题,与编译器有很大的关系,一般来说,编译器为了方便cpu读取数据,会在编译时对struct中的字段进行对齐处理,对齐的规则是:字段的首字节地址是为k的整数倍,这个k一般取值为字段所占的字节数。如int字段占4位,首字节应该是4的整倍数;char字段占1个字节,首字节应该是1的整倍数。例如,对于结构

struct a { char c; int n; };

c占1个字节,n占4个字节,然而由于对齐问题,sizeof(struct

a)的值不是5,而应该是8,因为n的首字节要是4的倍数,在c和n中间空了3个没用的字节。这样虽然造成了空间的浪费,但是对于cpu的读取来说却方便了很多。

以前我只是理解到了这一层,这还远远不够,这只是struct内部字段的对齐,还没有涉及到整个struct的对齐。对整个struct结构的对齐,不同的编译器就做了不同的处理,主流的两大c编译器vc和gcc在处理上就有很大的不同。先看一个结构吧

struct b { char c; double d; int

n; };

按照刚才的理解,这个结构占的字节数sizeof(struct

b)应该是20.double与8对齐,int与4对齐。但是实际的运行结果却大不相同,可以验证,在vc下面的运行结果是24,而在gcc下的结果是16,差了8个字节呢。为什么呢?查阅了不少的资料,有所收获。

原来,gcc和vc在这个问题上的处理是有所不同的。还是回到最初的字节对齐问题,不过有些规则得改一下,引入一个对齐模数的概念,对齐模数就是指那个k。在vc上,还是原来的套路,char的对齐模数是1,int,long,float的对齐模数都是4,double的对齐模数是8;但是gcc上就不一样了,char的对齐模数是1,int,long,float的对齐模数都是4,double的对齐模数不是8,而是4,原来在gcc中,没有大于4的对齐模数。

这样一来,再回去解释刚才的问题sizeof(struct

b),在gcc下就通了,char与1对齐,double与4对齐,int与4对齐,(1+3) + 8 + 4 =

16,在char和double之间补了3个没用的字节。但是在vc下面还是不通,这样算来结果还是20,与那个24还有出入。

原因就是还有另外一种对齐,就是结构整体的对齐,整个结构也有一个对齐模数,这个对齐模数的取值是各字段对齐模数的最大值。整个结构的大小必须是结构对齐模数的整数倍。在gcc上也有对齐模数的概念,定义也是一样的,但是取值的范围不同,gcc的最大对齐模数是4。

这样就清楚了,在vc上,struct

b的对齐模数是8,所以应该是8的整数倍,所以应该是24。于是就清楚了。

找几个验证一下:

strcut c { int n; char

a;}; //gcc: 8 vc:8

struct d { double s; char

a}; //gcc:12 vc:16

---------------------------------###一个华丽的分隔符####------------------------------------

从这一下开始c语言数组的问题。先看问题:

int a[5] = {1,2,3,4,5};

int *p =

(int*)(&a + 1);

printf("%d", *(a+1));

printf("%d",

*(p-1));

嗯,结果是2 5,有点出乎意料吧?不够出乎意料,再来一个:

int a[5] = {1,2,3,4,5};

printf("%p\n", a);

printf("%p\n", &a);

两个printf的输出结果是一样的,这回够意外了吧?真的是这样,不信可以试试。

这是为什么呢?首先看一下c语言对数组的解释:在c语言里,数组当成一个整体对待,数组名a就代表这个数组,所以对a取地址&a应该是对这个数组取地址,自然取的是首地址;同时a还代表这个数组的首地址,所以a和&a是一样的。数组名a代表数组的首地址是毋庸置疑的,都可以接受,但是数组名也代表整个数组又怎么理解呢?有证据,看这个sizeof(a),这个大小是20,整个数组的大小,而不是一个地址的大小。所以数组名代表整个数组也是有据可循的。

这样就解释了第二个问题:a和&a一样!

第一个问题,还是有点乱,一点一点来理清楚吧。

这里还涉及到一个问题,就是对指针加减操作的步长问题,即对某个指针+1,地址会加多少。这个步长与指针的类型有关系,对于int型指针,步长就是4字节,double型指针是8,struct的步长为sizeof(struct)。

好了,可以开始解释了,第一句话很简单,初始化一个数组,没问题的。第二句,需要好好想想了,大体是声明了一个int型指针,然后赋了初值,关键是这个初值是什么?有一个&a,这是对a取地址,即取了一个指向a的指针,a的类型是啥?int型?不对,应该是一个int数组。所以这个指针的类型是int[5]*,步长就是sizeof(a),即20个字节。所以&a+1,指向的地址是把a的地址向后偏移一个步长20个字节(这个可以用printf打印出来看的,可以试验)。这样第二句就可以解释了,把a向后偏移20个字节,然后把地址强装成int型指针,赋给p作为初值。第三句,这时的a又不是整个数组了,而是代表数组的首地址,相当于一个int型指针,所以直接+1,步长应该是sizeof(int),再取值打印,结果自然就是第二个元素2了。第四句,对p-1,现在p是一个int型指针,所以-1应该是向前偏移1个int的长度,然后取值打印,先前p是对a向后偏移20个字节,现在又往回偏移了4个字节,所以一共向后偏移了16个字节,即取到了数组的第五个元素,所以结果是5。

问题解释清了,虽然有点乱,但是只要记住对于一个数组a来说,a既代表这个数组整体,又代表这个数组的首地址。怎么区分呢,看操作,进行取地址操作的时候,a就代表整个数组;直接进行偏移或者其他地址操作的时候就代表首地址了。

就这么多了,写了好久,应该记得很清楚了。c语言的博大精深,我才理解了这么一点,遇到问题,解决问题,需要很大的耐心,我要走的路还很长。

写于北航,2011年1月16日

by Jason

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值