#include<stdio.h>
void foo(int **arr) {
arr[1][1]++;
}
main() {
int arr[20][20];
printf("%d\n",arr[1][1]);
foo((int**)arr);
printf("%d\n",arr[1][1]);
}
Предположим, вы объявляете: int arr[ 10 ][ 20 ] ;
Какой тип у arr?
Вы можете подумать, что этоint **
, но это неверно.На самом деле он имеет тип
int (*)[20]
, когда он распадается (например, когда вы передаете его в функцию);
Распад массива применяется только один раз.
Теперь рассмотрим следующее,
#include<stdio.h>
#include<stdlib.h>
void foo(int arr[][20]) {
arr[1][1]++;
}
main() {
int (*arr)[20];
arr = malloc(sizeof(int (*)[]) * 2); //2 rows & malloc will do implicit cast.
printf("%d\n",arr[1][1]);
foo(arr);
printf("%d\n",arr[1][1]);
}
Выход:
$ gcc fdsf.c && ./a.out
0
1
arr и arr+1 указывают на массив из 20 целых чисел.
arr + 0 --> int int int ... int (20 ints, contiguous)
[0][0] [0][1]
arr + 1 --> int int int ... int (20 ints, contiguous)
[1][0] [1][1][1]
foo
должен знать размер массива (ну, по крайней мере, второе измерение массива, первое не требуется), иначе он не сможет выполнить необходимую арифметику указателя для [1][1]
.
Вот как выглядит int[2][2]
в памяти:
int[2] int[2]
То есть массив, за которым сразу следует другой массив.
Вот как выглядит int[2]
в памяти:
int int
То есть int, за которым сразу следует другой int.
Вот также выглядит int[2][2]
в памяти:
int int int int
^ ^
| |___ this is arr[1][1]
|
|____ this is p[1], assuming sizeof(int*) == sizeof(int)
Если привести arr
к int**
, то я назову результат p
. Тогда он будет указывать на ту же память. Когда вы делаете p[1][1]
, вы не получаете arr[1][1]
. Вместо этого программа считывает значение в p[1]
, корректирует его на размер int и разыменовывает его. Если второй int содержит, скажем, значение "21", то вы только что пытались разыменовать указатель "25" (если int
имеет размер 4 байта). Это неправильно.
Массивы - это не то же самое, что указатели, а двумерные массивы - это, конечно, не то же самое, что указатели на указатели.
Потому что foo ожидает указатель на указатель на int, а вы передаете ему указатель на массив из 20 int. Приведение его не изменит того факта, что это неправильный тип.
Если вы измените его таким образом, вы получите ожидаемый результат:
#include<stdio.h>
void foo(int arr[][20]) {
arr[1][1]++;
}
int
main() {
int arr[20][20];
arr[1][1] = 1;
printf("%d\n",arr[1][1]);
foo(arr);
printf("%d\n",arr[1][1]);
}
Проблема в том, что int arr [20] [20]
для 2d-массива означает, что этот массив хранится как 1d-массив, а строки сохраняются одна за другой. когда вы выполняете индексацию до int ** arr
, вы фактически берете 2-й элемент из первой строки массива, затем разыменовываете его и берете там первый элемент.