为什么我们在传递动态 2D 数组时不需要列数?

Why we don't need number of column when passing the dynamic 2d array?

本文关键字:不需要 数组 2D 我们 动态 为什么      更新时间:2023-10-16

假设我有两个数组,我将它们传递给一个函数:

void func(int arr1[][4], int **arr2) { // <- I need to give n in only one, why?
...
}
int main() {
int n = 5, m = 4;
int arr1[n][m];
int **arr2 = (int**)malloc(n * sizeof(int*));
for(int i = 0;i < n;i++)
arr2[i] = (int*)malloc(m * sizeof(int));
func(arr1, arr2);
return 0;
}

为什么我们不能以类似的方式处理两个数组传递?

编辑:代码中存在错误。

与你所说的相反的是:你不必传递行数。假设数组索引的工作方式如下:

int arr[MAX_ROW][MAX_COL]; /* with both 3 */
col
--------------->
| 0,0   0,1   0,2
row | 1,0   1,1   1,2
V 2,0   2,1   2,2

当您传递int arr[][MAX_COL]编译器知道当您像arr[row][col]这样的地址时下一行将从哪里开始。

如果您使用指针手动执行此操作,它将如下所示:&arr[0][0] + row * MAX_COL + col.在该示例中,您还必须知道数组的列大小MAX_COL才能计算下一行。

这样做的原因是,数组在内存中是连续的。上面的数组在内存中表示,如下所示:

|     row = 0     |     row = 1     |     row = 2     |
| 0,0   0,1   0,2 | 1,0   1,1   1,2 | 2,0   2,1   2,2 |

编译器还必须知道行偏移量,因为当您将声明为int arr[MAX_SIZE]的数组传递给函数void foo (int arr[])时,它会衰减为指向数组开头的指针int* arr。在数组数组(2D 数组)的情况下,它也衰减到指向其第一个元素的指针,该元素是指向单个数组int (*arr)[MAX_COL]的指针。

简而言之:有了int arr[][MAX_COL]编译器就拥有使用arr[row][col]对数组进行寻址所需的所有信息。

实际上恰恰相反,您只能省略索引中的一个(在多维数组的情况下),即最里面的索引。

这是因为,数组虽然作为函数参数传递,但衰减到指向第一个元素的指针。引用C11,章节§6.3.2.1

除非它是sizeof运算符、_Alignof运算符或 一元&运算符,或者 是用于初始化数组的字符串文本,该表达式具有 类型"类型数组"转换为类型为"指向类型的指针">的表达式,该表达式指向 到数组对象的初始元素,并且不是左值。[...]

因此,像这样的符号

void func(int arr1[][5], int **arr2)    //an array of array of 5 ints

void func(int (*arr1) [5], int **arr2)  //pointer to the starting element of an array of 5 ints

是等效的。

你实际上只有一个整数数组(即int arr1[][5]) 和一个指向 int 指针的指针,即int **arr2.即使像arr1[10][5]这样的数组,当作为参数传递给函数时,衰减到指向元素所在的内存开头的指针,内存布局和编译器处理这些指针的方式也存在(很大)差异。

顺便说一句,主要是它应该是int n=5,m=4;int arr1[m][n],而不是int n=5,m=4;int arr1[n][m].

关于内存布局:

形式为int [10][5]的 2D 整数数组表示为 10 个连续的"行",每个"行"包含 5 个"列"(即整数值)。这个数组的大小是10 * 5 * sizeof(int),一个"行"的大小是5 * sizeof(int)

指向 intint **p的指针的指针只是一个指针; 它的大小是sizeof(int**)的,即使你已经"错误定位"了一连串的整数指针 lilep = malloc(10 * sizeof (int*));请注意sizeof(int *)中的"*",因为您创建指向整数的指针序列,而不是整数序列。这是内存布局的主要区别:它不是整数的 2D 数组,而是指向整数的指针的 1D 数组。如果实际上为"10"个整数分配了 10 个"行",则每行可能位于内存的不同部分。管理这样一个(分散的)10x5整数值所需的空间是"10*sizeof(int*) + 10*5*sizeof(int)"。

关于访问:

让我们假设一个类型int arr[][5]的变量,它是一个整数的 2D 数组,其中列的大小是5的,行数是不确定的。非正式地,像int x = arr[3][4]这样的访问被转换为数组第(3*5 + 4)个元素的访问,即"行乘以行大小加列";请注意,基于此公式,编译器不需要知道数组实际有多少行。

相反,让我们假设一个类型int **p的变量。您可以将像x = p[3][4]这样的访问视为等效于int *r = p[3]; int x = r[4];请注意,r的类型为int *,即它是一个指针,然后r[4]取消引用此指针并返回一个整数值。

这是相当非正式的描述。 然而,主要问题是arr[][5]的内存布局仅包含连续的整数值,而int **arrr可能是指针的序列(甚至只是一个这样的指针),每个指针都可能指向一系列整数值(或仅一个整数值)。