关于C#:如何在内存中格式化多维数组?

How are multi-dimensional arrays formatted in memory?

在C中,我知道可以使用以下代码在堆上动态分配二维数组:

1
2
3
4
5
int** someNumbers = malloc(arrayRows*sizeof(int*));

for (i = 0; i < arrayRows; i++) {
    someNumbers[i] = malloc(arrayColumns*sizeof(int));
}

显然,这实际上创建了一个指向一组单独的一维整数数组的一维指针数组,"系统"可以在我要求时理解我的意思:

1
someNumbers[4][2];

但是当我静态地声明一个二维数组时,如下面的一行…:

1
int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];

…类似的结构是在堆栈上创建的,还是完全是另一种形式?(即,它是一个一维指针数组吗?如果没有,它是什么?如何找到它的引用?)

另外,当我说,"系统",什么才是真正的责任来解决这个问题?内核?还是C编译器在编译时对其进行排序?


一个静态的二维数组看起来像一个数组数组-它只是在内存中连续排列。数组与指针不是一回事,但由于您经常可以互换使用它们,因此有时会混淆。不过,编译器保持正确的跟踪,这使得所有的东西都很好地排列起来。您必须像前面提到的那样小心处理静态的二维数组,因为如果您试图将一个数组传递给一个接受int **参数的函数,就会发生不好的事情。下面是一个简单的例子:

1
int array1[3][2] = {{0, 1}, {2, 3}, {4, 5}};

内存如下:

1
0 1 2 3 4 5

完全相同:

1
int array2[6] = { 0, 1, 2, 3, 4, 5 };

但如果您试图将array1传递给此函数:

1
void function1(int **a);

您将收到警告(应用程序将无法正确访问阵列):

1
warning: passing argument 1 of ‘function1’ from incompatible pointer type

因为二维数组与int **不同。可以说,数组自动衰减成指针的过程只需要"一级深"。您需要将函数声明为:

1
void function2(int a[][2]);

1
void function2(int a[3][2]);

让一切都快乐。

同样的概念也适用于N维数组。不过,在应用程序中利用这种有趣的业务通常只会使您更难理解。所以在外面要小心。


答案是基于这样一个想法:C没有真正的二维数组——它有数组。当你申报时:

1
int someNumbers[4][2];

您要求someNumbers是一个由4个元素组成的数组,其中该数组的每个元素都是int [2]类型(它本身是一个由2个int组成的数组)。

另一个难题是数组总是在内存中连续排列。如果你要求:

1
sometype_t array[4];

那么,这看起来总是这样:

1
| sometype_t | sometype_t | sometype_t | sometype_t |

(4个sometype_t物体排列在一起,中间没有空间)。因此,在您的someNumbers数组中,它将如下所示:

1
| int [2]    | int [2]    | int [2]    | int [2]    |

每个int [2]元素本身就是一个数组,看起来像这样:

1
| int        | int        |

所以总的来说,你会得到:

1
| int | int  | int | int  | int | int  | int | int  |


1
unsigned char MultiArray[5][2]={{0,1},{2,3},{4,5},{6,7},{8,9}};

内存等于:

1
unsigned char SingleArray[10]={0,1,2,3,4,5,6,7,8,9};

同时回答您的问题:两者都是,尽管编译器正在做大部分的繁重工作。

对于静态分配的数组,"系统"将是编译器。它将像为任何堆栈变量那样保留内存。

对于malloc的数组,"系统"将是malloc的实现者(通常是内核)。编译器将分配的所有内容都是基指针。

编译器总是按照声明的方式来处理类型,除非在Carl给出的示例中,它可以找出可互换的用法。这就是为什么如果您将一个[[]传递给一个函数,它必须假定它是一个静态分配的平面,其中**假定为指向指针的指针。


要访问特定的二维数组,请考虑数组声明的内存映射,如下代码所示:

1
2
3
    0  1
a[0]0  1
a[1]2  3

要访问每个元素,只需将感兴趣的数组作为参数传递给函数即可。然后对列使用偏移量来单独访问每个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int a[2][2] ={{0,1},{2,3}};

void f1(int *ptr);

void f1(int *ptr)
{
    int a=0;
    int b=0;
    a=ptr[0];
    b=ptr[1];
    printf("%d
"
,a);
    printf("%d
"
,b);
}

int main()
{
   f1(a[0]);
   f1(a[1]);
    return 0;
}