这个指针算术如何工作?
#include int main(void){ unsigned a[3][4] = { {2,23,6,7}, {8,5,1,4}, {12,15,3,9} }; printf("%u",*((int*)(((char*)a)+4))); return 0; }
我的机器中的输出是a[0][1]
的值,即23。可以有人解释这是如何工作的?
编辑:回滚到旧的yucky
代码,正是呈现给我的:P
所以你把你的数组放在内存中:
2, 23, 6, 7, 8...
这样做是将数组转换为char*
,它允许您访问单个字节,它指向此处:
2, 23, 6, 7, 8... ^
然后它添加四个字节,将其移动到下一个值(稍后将详细介绍)。
2, 23, 6, 7, 8... ^
然后它将它变成一个int*
并取消引用它,得到值23。
这个代码在技术上有三个问题。
第一个是它假定unsigned
大小是4个字节。 (因此+ 4
)。 但这不一定是真的! 更好的是+ sizeof(unsigned)
,无论unsigned
大小如何,都要确保正确性。
第二个问题是转换为int
:原始数组是unsigned
,但是值被强制转换为int
。 在unsigned
范围内存在int
无法表示的值(因为在范围的int
半部分中是负数。)因此,如果数组中的某个值不能表示为int
(意味着该值大于INT_MAX
) ,你会得到错误的价值。 更好的是转换为unsigned*
,以保持正确的类型。
最后一件事是格式说明符。 整数的说明符是%d
,但代码使用%u
,这是无符号整数。 实际上,即使转换回int*
是错误的, printf
也会将该值转换回unsigned*
,恢复它的完整性。 通过解决问题二,问题三解决了问题。
有一个隐藏的第四个问题:代码很糟糕。 这可能是出于学习目的,但是很糟糕 。
数组:
unsigned a[3][4] = { {2,23,6,7}, {8,5,1,4}, {12,15,3,9} };
在内存中布局为(假设a
本身位于内存位置0x8000
,一个特定的字节序和一个四字节的int
):
0x8000 0 0 0 2 0x8004 0 0 0 23 0x8008 0 0 0 6 0x800C 0 0 0 7 0x8010 0 0 0 8 0x8014 0 0 0 5 0x8018 0 0 0 14 0x801C 0 0 0 12 0x8020 0 0 0 15 0x8024 0 0 0 3 0x8028 0 0 0 9
打破表达方式:
*((int*)(((char*)a)+4))
-
((char*)a)
给你一个char
指针。 -
+4
使指针前进4个字节(4 * sizeof(char)
) -
(int*)
将其结果转换回int
指针。 -
*
取消引用指针提取int
。
这是一种非常愚蠢的方式,因为它本质上是不可移植的(例如,对于int
为2或8个字节的环境)。
它首先隐式地将数组a转换为指向其开头的指针。 然后它将指针强制转换为char *并将值递增4.值4恰好与系统上的sizeof(unsigned)相同,所以实际上它已经从firstn向前移动了一个元素。 然后它将地址转换为int *并读取它指向的值(operator *)。 此结果值将打印为无符号整数,这可以正常工作,因为int和unsigned的大小相同。
存储器中静态2Darrays的布局使得所有元素实际上按顺序存储为一维arrays。
unsigned int的大小为4.即sizeof(unsigned)== 4
它可以容纳4个字符,每个字符都是一个字节[在C中不在Java / C#等]。
数组在内存中连续分配。 将无符号数组视为char *时,需要将指针移动4步以到达数组中的下一个无符号值。
首先,创建一个大小为3×4的2-dim数组。
在((char*)a)
您可以将其作为char数组使用。 我们将其指定为b。
((char*)a)+4
与b[4]
相同,它指向char数组的第5
个元素(你记得,C中的aarays是基于0的)。 或者只是第5个字节。
当您将数组转换回int时,如果sizeof(int) = 4
,则int数组i-th
元素从i*4
字节开始。 因此,在第5个字节处,int数组的第二个元素开始于指针指向的位置。 编译器从第4个位置开始获取4个字节并表示它是int。 那正好是[0] [1]。