这个指针算术如何工作?

#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)+4b[4]相同,它指向char数组的第5个元素(你记得,C中的aarays是基于0的)。 或者只是第5个字节。

当您将数组转换回int时,如果sizeof(int) = 4 ,则int数组i-th元素从i*4字节开始。 因此,在第5个字节处,int数组的第二个元素开始于指针指向的位置。 编译器从第4个位置开始获取4个字节并表示它是int。 那正好是[0] [1]。