多维数组和指针数学

Multidimensional array and pointer math

鉴于此代码:

#include <stdio.h>
int main()
{
    char a[3][5] = {2, 7, 3, 9, 5,
                    1, 2, 3, 4, 5,
                    6, 7, 8, 9, 10};
    printf("sizeof(a)=%d, sizeof(&a)=%d, sizeof(a[0])=%d\n", sizeof(a),
           sizeof(&a), sizeof(a[0]));
    printf("a+1=%p, a[0]+1=%p, &a+1=%p, &a[0]+1=%p\n", a+1, a[0] + 1, &a+1,
           &a[0]+1);
}

如果 a 从 0x1000 开始,并且 char 是 1 个字节,输出会是什么?真正的问题是 为什么 打印这些数字,尤其是。最后一行。

我已经执行了程序并观察了输出。

sizeof(a)=15, sizeof(&a)=8, sizeof(a[0])=5 a+1=0x7ffc7d4dfc15, a[0]+1=0x7ffc7d4dfc11, &a+1=0x7ffc7d4dfc1f, &a[0]+1=0x7ffc7d4dfc15

这里的问题是为什么数字不同。例如:为什么 a[0]+1 和 &a+1 有区别?

好吧,一些问题是由错误的格式说明符引起的。正如 Vlad 所指出的,对 sizeof 的结果使用 %zu 很重要,因为在某些系统(64 位?)上它可能不是 int。如果您对 printf 使用多个参数,这些事情很重要,因为堆栈布局将不是 printf 所期望的。 单个 小值可能会在小端系统上正常打印,即使使用错误的整数长度转换说明符,因为第一个参数的位置是已知的,并且重要性较低的字节在前。

但是您评论中的一个问题很容易回答,此外:

Why is there a difference between a[0]+1 and &a+1?

原因是a[0]是5个int的数组(矩阵是3个5个int的一维数组的序列)。它衰减为指向 int 的指针。向该指针添加 1 会将其推进到下一个 int,这可能是通过为其数值添加 4。

相比之下,&a 不是数组或矩阵,而是一个地址——整个矩阵的地址。数值与&(a[0])&(a[0][0])相同;但它的类型不同。它确实是指向整个矩阵作为单个对象的指针。 (这种做法很少见,但定义明确且合法。)

矩阵的大小是 15 个整数,大概是 60 个字节。向指针添加 1 会将其推进到 "the next matrix",可能是通过向地址添加数字 60。

以下是关于指针运算的简短教程,因为它对于理解事物至关重要。建议尝试一下。

  1. P+n,其中 P 是指针,n 是某个整数值,结果比 Pn*sizeof(*P) 个字节。结果的类型与P的类型相同。这是指针运算最基本的情况。
  2. A+n,其中 A 是一个数组,n 是某个整数值,结果比 An+sizeof(A[0]) 个字节。结果的类型是指向 A[0] 类型的指针。或者,您可以说 A+n&A[0]+n 相同。这被称为 "array decay",因为表达式 A "decays" 指向指向其第一个元素的指针(衰减为 &A[0])。例如,如果 A 的类型为 int[20][40],它将衰减为类型 int(*)[40]

现在我们可以继续了。

  • sizeof(a)=15
    sizeof(a)得到15是因为char类型的项目有3*5=15个,而sizeof(char)就是1个,简单来说就是sizeof(array) == count*sizeof(type)。在多维数组的情况下,您只需将计数相乘并创建一个大计数,得到 sizeof(a) == (3*5)*sizeof(char)sizeof(a) == (15)*1.

  • sizeof(&a)=8
    &achar (*)[3][5] 或 "pointer to 3 arrays of 5 chars" 类型。因为它是一个指针,所以它将是您机器上指针大小的任何值。在这种情况下,它是 8(这意味着您可能 运行 在 64 位平台上)。

  • sizeof(a[0])=5
    a 是 3 个 5 个字符的数组,所以 a[0] 得到第一个 5 个字符的数组。如前所述,sizeof(array) == count*sizeof(type),所以sizeof(a[0]) == 5*sizeof(char),或5.

  • a+1=0x7ffc7d4dfc15
    请记住,指针运算会强制数组衰减到指向其第一个元素的指针。所以将 a 重写为 &a[0]。现在向该指针添加 1,这会将 1*sizeof(a[0]) 或 5 添加到地址。如果从打印的结果中减去 5,就会显示 a 本身的地址:0x7ffc7d4dfc10。这对于本答案的其余部分很重要。

  • a[0]+1=0x7ffc7d4dfc11
    如前所述,a0x7ffc7d4dfc10 开始,a[0] 获取第一个 5 个字符的数组。由于 a[0]char[5] 类型并且应用了指针算法,因此发生数组衰减。基本上,您现在有 &a[0][0](类型:char *)而不是 a[0]。这使事情变得简单:0x7ffc7d4dfc10 + 1*sizeof(a[0][0])0x7ffc7d4dfc10 + 1*1 = 0x7ffc7d4dfc11.

  • &a+1=0x7ffc7d4dfc1f
    同样,& 检索指向 a 的指针。 a 的类型为 char[3][5](3 个 5 个字符的数组),因此 &a 的类型为 char(*)[3][5](指向 3 个 5 个字符的数组的指针)。将 1 添加到此结果 0x7ffc7d4dfc10 + 1*sizeof(char[3][5]) = 0x7ffc7d4dfc10 + (3*5)*sizeof(char) = 0x7ffc7d4dfc10 + 15。 15的十六进制是0x0f,所以0x7ffc7d4dfc10 + 0x0f = 0x7ffc7d4dfc1f.

  • &a[0]+1=0x7ffc7d4dfc15
    a[0] 的类型为 char[5]&a[0] 的类型为 char(*)[5]。向该指针加 1 会导致添加 1*sizeof(a[0]),即 5,从而导致 0x7ffc7d4dfc10 + 5 = 0x7ffc7d4dfc15.

好的,这是我的解释版本。理解多个数组中的地址的关键是一些规则:

规则 1:

如果 p 是指向数组某个元素的指针,则 p++ 递增 p 以指向下一个元素..(K&R sec 5.4)和 如果表达式或子表达式的类型是 "array of T",对于某个 T,则表达式的值是指向数组中第一个对象的指针,并且表达式的类型更改为指向 T.(K&R,A7.1 节)

加起来,这意味着如果有一个数组:

T array[arraysize];

对于某些类型 T,则数组变为(有一些例外)

T *ptr;

和 (array + 1) = (ptr + 1) = ptr_value + 1 x sizeof(T)。 如果那没有意义,假设有一个

char str[] = "hello!";

则str + 1 = str_addr + 1 x sizeof(char) = str_addr + 1指向字母'e'.

规则 2:

在 C 中,二维数组实际上是一维数组,其每个元素都是一个数组(K&R,第 5.7 节)

规则 3:

当 sizeof 应用于数组时,结果是数组中的总字节数(K&R,第 A7.4.8 节)。请注意,这是上述规则 1 中提到的例外情况之一。

规则 4:

对于任何类型的数组:

T array[arraysize];

&array 的类型为 T (*) [size]。规则 1 告诉我们 &array + 1 将是 (array_addr + arraysize) 。 (K&R中没有很好的解释,我认为这是理解这些问题的根本原因)。

现在让我们看看原来的问题。假设数组 a[] 从内存位置 A 开始(本例中为 0x7ffc7d4dfc10)。

规则 2 告诉我们 a[] 实际上是一个包含 3 个元素的数组,每个元素都是一个大小为 5 的数组。 假设有一个:

typedef char char5_t[5];

那么a[]可以表示为:

char5_t a[3];

所以使用规则 3,sizeof(a) = 3 x sizeof(char5_t) = 3 x 5 = 15.

sizeof(&a) 是一个指针的大小 = 8(64 位)在这个例子中因为我是 运行 它在 64 位机器上。

sizeof(a[0]) 就是 char5_t = 5 的大小。您也可以应用规则 3 来获得 sizeof(char[5]) = 5.

要计算 (a + 1),请记住 a[] 可以重写为 char5_t a[3]。 规则 1 告诉我们 a + 1 = A + 1 x sizeof(char5_t) = A + 5 = 0x7ffc7d4dfc15.

要计算 a[0] + 1,假设有一个 char5_t a0 = a[0]; 由于 a0 的实际类型是 char[5],规则 1 告诉我们 (a0 + 1) = A + 1 * sizeof(char) = 0x7ffc7d4dfc11.

&a + 1 由规则 4 提供,即 A + 15 = 0x7ffc7d4dfc10 + 15 = 0x7ffc7d4dfc1f.

&a[0] 相当于指向 char5_t 的指针。使用规则 1,&a[0] + 1 = A + 1 * sizeof(char5_t) =

A + 5 = 0x7ffc7d4dfc15.