多维数组和指针数学
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。
以下是关于指针运算的简短教程,因为它对于理解事物至关重要。建议尝试一下。
P+n
,其中 P
是指针,n
是某个整数值,结果比 P
多 n*sizeof(*P)
个字节。结果的类型与P
的类型相同。这是指针运算最基本的情况。
A+n
,其中 A
是一个数组,n
是某个整数值,结果比 A
多 n+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
&a
是 char (*)[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
如前所述,a
从 0x7ffc7d4dfc10
开始,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.
鉴于此代码:
#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。
以下是关于指针运算的简短教程,因为它对于理解事物至关重要。建议尝试一下。
P+n
,其中P
是指针,n
是某个整数值,结果比P
多n*sizeof(*P)
个字节。结果的类型与P
的类型相同。这是指针运算最基本的情况。A+n
,其中A
是一个数组,n
是某个整数值,结果比A
多n+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
&a
是char (*)[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
如前所述,a
从0x7ffc7d4dfc10
开始,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.