通过指向不完整数组类型的指针将多维数组传递给没有最大大小的函数
Passing multi-dimensional array to function without righmost size by pointer to incomplete array type
据我所知,数组总是作为指针传递。例如声明:
void foo(int array[2][5]);
对编译器的意义与:
完全相同
void foo(int (*array)[5]);
你可以说这两种形式是等价的。现在,我想知道为什么允许将其声明为:
void foo(int (*array)[]);
而不是:
void foo(int array[][]);
举个例子:
#include <stdio.h>
void foo(int (*p)[3]);
void bar(int (*p)[]);
int main(void)
{
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
foo(a);
bar(a);
return 0;
}
// The same as int p[][3] or int p[N][3] where N is a constant expression
void foo(int (*p)[3])
{
}
// Would it the same as int p[][] or int p[N][] (by analogy)?
void bar(int (*p)[])
{
}
它编译正常并且没有警告,但是如果我将 bar
的声明更改为:
void bar(int p[][]);
那就报错了。
为什么 C 允许这种 "obscure" 方式来传递数组?
数组不是指针,如果你声明一个未指定大小的指针数组也没关系,因为它将连续存储存储在指针中的地址并且每个元素的大小是已知的,但是 p[][]
需要数组是连续的而不是它们的地址并且数组的大小是未知的所以这就是问题所在。
为了清楚起见,如果你说 int p[][]
你不知道 p[0]
离 p[1]
有多远,而在 int (*p)[]
你知道距离是指针的大小。
数组被转换为指针,但不是指针。
数组可以用作指针,但它们不仅仅是指针;他们指向许多有大小的东西。这样,您可以告诉编译器您对数组的第三个或第五个元素感兴趣,它会计算该元素的位置,方法是将一个元素的大小乘以三或五以找到偏移量,然后将其相加数组地址的偏移量。
C 实际上没有多维数组。它所拥有的是数组的数组,这几乎是一回事。假设你有这个:
int a[4][5];
在记忆中,这看起来像这样:
[0] |[1] |[2] |[3]
+-------------------|-------------------|-------------------|-------------------+
a | | | | | | | | | | | | | X | | | | | | | |
+-------------------|-------------------|-------------------|-------------------+
0 1 2 3 4 | 0 1 2 3 4 | 0 1 2 3 4 | 0 1 2 3 4 |
然后您尝试访问索引 [2][2]
处的元素,那么系统需要执行以下计算(概念上):
- 取一个 int 的大小,并将其乘以 5 得到内部数组的大小
- 取内部数组的大小,乘以二得到外部数组中第二个元素的偏移量
- 再取一个 int 的大小,乘以二得到内部数组中第二个元素的偏移量
- 加上两个偏移量;那是你的内存地址
在这种情况下,计算是*(a + (2 * 5 * sizeof(int)) + (2 * sizeof(int)))
,给出*(a + 12*sizeof(int))
,这确实是从起始指针的正确偏移。
这意味着需要定义内部数组的大小,以便编译器能够进行该计算。如果你没有定义多维数组的任何维度(但最左边的维度)的大小,你就没有定义的大小,编译器会犹豫不决。
你被 C 语言支持 'unspecified' 大小的数组绊倒了,你可以声明它,但通常不能直接使用,除非你在某处给它一个更具体的大小。
在您的示例中,如果您实际尝试对 bar
中的数组执行任何操作,您将收到错误消息:
void bar(int (*p)[])
{
printf("%d\n", p[1][1]);
}
% gcc -Wall t.c
t.c: In function ‘bar’:
t.c:23:5: error: invalid use of array with unspecified bounds
printf("%d\n", p[1][1]);
^
在 bar 中使用 p
唯一可以做的就是将它转换为具有显式大小的某种类型,或者将它传递给其他一些函数,该函数采用指向数组(指定或未指定大小)的指针),并且如果您使用 错误的 显式大小来尝试访问该数组,您将得到未定义的行为且没有警告。
据我所知,数组总是作为指针传递。例如声明:
void foo(int array[2][5]);
对编译器的意义与:
完全相同void foo(int (*array)[5]);
你可以说这两种形式是等价的。现在,我想知道为什么允许将其声明为:
void foo(int (*array)[]);
而不是:
void foo(int array[][]);
举个例子:
#include <stdio.h>
void foo(int (*p)[3]);
void bar(int (*p)[]);
int main(void)
{
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
foo(a);
bar(a);
return 0;
}
// The same as int p[][3] or int p[N][3] where N is a constant expression
void foo(int (*p)[3])
{
}
// Would it the same as int p[][] or int p[N][] (by analogy)?
void bar(int (*p)[])
{
}
它编译正常并且没有警告,但是如果我将 bar
的声明更改为:
void bar(int p[][]);
那就报错了。
为什么 C 允许这种 "obscure" 方式来传递数组?
数组不是指针,如果你声明一个未指定大小的指针数组也没关系,因为它将连续存储存储在指针中的地址并且每个元素的大小是已知的,但是 p[][]
需要数组是连续的而不是它们的地址并且数组的大小是未知的所以这就是问题所在。
为了清楚起见,如果你说 int p[][]
你不知道 p[0]
离 p[1]
有多远,而在 int (*p)[]
你知道距离是指针的大小。
数组被转换为指针,但不是指针。
数组可以用作指针,但它们不仅仅是指针;他们指向许多有大小的东西。这样,您可以告诉编译器您对数组的第三个或第五个元素感兴趣,它会计算该元素的位置,方法是将一个元素的大小乘以三或五以找到偏移量,然后将其相加数组地址的偏移量。
C 实际上没有多维数组。它所拥有的是数组的数组,这几乎是一回事。假设你有这个:
int a[4][5];
在记忆中,这看起来像这样:
[0] |[1] |[2] |[3]
+-------------------|-------------------|-------------------|-------------------+
a | | | | | | | | | | | | | X | | | | | | | |
+-------------------|-------------------|-------------------|-------------------+
0 1 2 3 4 | 0 1 2 3 4 | 0 1 2 3 4 | 0 1 2 3 4 |
然后您尝试访问索引 [2][2]
处的元素,那么系统需要执行以下计算(概念上):
- 取一个 int 的大小,并将其乘以 5 得到内部数组的大小
- 取内部数组的大小,乘以二得到外部数组中第二个元素的偏移量
- 再取一个 int 的大小,乘以二得到内部数组中第二个元素的偏移量
- 加上两个偏移量;那是你的内存地址
在这种情况下,计算是*(a + (2 * 5 * sizeof(int)) + (2 * sizeof(int)))
,给出*(a + 12*sizeof(int))
,这确实是从起始指针的正确偏移。
这意味着需要定义内部数组的大小,以便编译器能够进行该计算。如果你没有定义多维数组的任何维度(但最左边的维度)的大小,你就没有定义的大小,编译器会犹豫不决。
你被 C 语言支持 'unspecified' 大小的数组绊倒了,你可以声明它,但通常不能直接使用,除非你在某处给它一个更具体的大小。
在您的示例中,如果您实际尝试对 bar
中的数组执行任何操作,您将收到错误消息:
void bar(int (*p)[])
{
printf("%d\n", p[1][1]);
}
% gcc -Wall t.c
t.c: In function ‘bar’:
t.c:23:5: error: invalid use of array with unspecified bounds
printf("%d\n", p[1][1]);
^
在 bar 中使用 p
唯一可以做的就是将它转换为具有显式大小的某种类型,或者将它传递给其他一些函数,该函数采用指向数组(指定或未指定大小)的指针),并且如果您使用 错误的 显式大小来尝试访问该数组,您将得到未定义的行为且没有警告。