这个指针算术如何工作?

时间:2010-01-24 08:23:01

标签: c++ c puzzle

#include <stdio.h>

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

5 个答案:

答案 0 :(得分:13)

所以你把你的数组放在内存中:

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,但值正在转换为intunsigned范围内存在int无法表示的值(因为int范围的一半是负数。)因此,如果数组中的某个值无法表示作为int(意味着值大于INT_MAX),您将获得错误的值。更好的是转换为unsigned*,以保持正确的类型。

最后一件事是格式说明符。整数的说明符是%d,但代码使用%u,它用于无符号整数。实际上,即使回退到int*错误,printf也会将 值转换回unsigned*,从而恢复其完整性。通过解决问题二,问题三解决了问题。

有一个隐藏的第四个问题:代码很糟糕。这可能是出于学习目的,但 yuck

答案 1 :(得分:9)

数组:

unsigned a[3][4] = {
    {2,23,6,7},
    {8,5,1,4},
    {12,15,3,9}
};

在内存中布局为(假设a本身位于内存位置0x8000,特定字节序和4字节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个字节的环境)。

答案 2 :(得分:2)

它首先隐式地将数组a转换为指向其开头的指针。然后它将指针转换为char *并将值递增4.值4恰好与系统上的sizeof(unsigned)相同,所以实际上它已经从一开始就向前移动了一个元素。然后它将地址转换为int *并读取它指向的值(operator *)。这个结果值打印为无符号整数,因为int和unsigned的大小相同。

内存中静态2D数组的布局使得所有元素实际上按顺序存储为一维数组。

答案 3 :(得分:1)

unsigned int的大小为4.即sizeof(unsigned)== 4

它可以容纳4个字符,每个字符都是一个字节[在C中不在Java / C#等]。

数组在内存中连续分配。将无符号数组视为char *时,需要将指针移动4步以达到数组中的下一个无符号值。

答案 4 :(得分:1)

首先,创建一个大小为3x4的2-dim数组。

((char*)a)之后,您可以将其作为char数组使用。我们将其指定为b。

((char*)a)+4b[4]相同,它指向char数组的5 - 元素(你记得,C中的aarays是基于0的)。或者只是第5个字节。

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