在C中,我知道我可以使用以下代码在堆上动态分配一个二维数组:
int** someNumbers = malloc(arrayRows*sizeof(int*));
for (i = 0; i < arrayRows; i++) {
someNumbers[i] = malloc(arrayColumns*sizeof(int));
}
显然,这实际上创建了一个一维数组的指针,指向一组单独的一维整数数组,“系统”可以找出我的意思,当我问:
someNumbers[4][2];
但是当我静态地声明一个2D数组时,如下所示…:
int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];
...是否在堆栈上创建了类似的结构,或者它完全是另一种形式?(例如,它是指针的1D数组吗?如果不是,它是什么,它的引用是如何被计算出来的?)
还有,当我说“系统”的时候,实际上是什么负责解决这个问题呢?内核?或者C编译器在编译时将其排序?
答案是基于这样一个想法,即C并没有真正的2D数组——它只有数组的数组。当你宣布:
int someNumbers[4][2];
您要求someNumbers是一个包含4个元素的数组,其中该数组的每个元素的类型为int[2](它本身是一个包含2个int的数组)。
这个难题的另一部分是数组总是在内存中连续地布局。如果你问:
sometype_t array[4];
那么它总是这样的:
| sometype_t | sometype_t | sometype_t | sometype_t |
(4个sometype_t对象彼此相邻,中间没有空格)。在你的someNumbers数组中,它看起来是这样的:
| int [2] | int [2] | int [2] | int [2] |
每个int[2]元素本身就是一个数组,它看起来像这样:
| int | int |
总的来说,你得到了这个:
| int | int | int | int | int | int | int | int |
假设,我们有a1和a2的定义和初始化如下(c99):
int a1[2][2] = {{142,143}, {144,145}};
int **a2 = (int* []){ (int []){242,243}, (int []){244,245} };
a1是一个在内存中具有普通连续布局的同构二维数组,表达式(int*)a1被求值为指向其第一个元素的指针:
a1 --> 142 143 144 145
a2从一个异构的2D数组初始化,是一个指向int*类型值的指针,即解引用表达式*a2计算为int*类型值,内存布局不必是连续的:
a2 --> p1 p2
...
p1 --> 242 243
...
p2 --> 244 245
尽管完全不同的内存布局和访问语义,c语言的数组访问表达式语法对于同质和异构的2D数组看起来完全相同:
表达式a1[1][0]将从a1数组中获取值144
表达式a2[1][0]将从a2数组中获取值244
编译器知道a1的访问表达式操作int类型[2][2],而a2的访问表达式操作int类型**。生成的程序集代码将遵循同构或异构访问语义。
当类型为int[N][M]的数组被类型转换并以int**类型访问时,代码通常会在运行时崩溃,例如:
((int**)a1)[1][0] //crash on dereference of a value of type 'int'
unsigned char MultiArray[5][2]={{0,1},{2,3},{4,5},{6,7},{8,9}};
在内存中等于:
unsigned char SingleArray[10]={0,1,2,3,4,5,6,7,8,9};
A static two-dimensional array looks like an array of arrays - it's just laid out contiguously in memory. Arrays are not the same thing as pointers, but because you can often use them pretty much interchangeably it can get confusing sometimes. The compiler keeps track properly, though, which makes everything line up nicely. You do have to be careful with static 2D arrays like you mention, since if you try to pass one to a function taking an int ** parameter, bad things are going to happen. Here's a quick example:
int array1[3][2] = {{0, 1}, {2, 3}, {4, 5}};
在内存中是这样的:
0 1 2 3 4 5
完全一样:
int array2[6] = { 0, 1, 2, 3, 4, 5 };
但如果你尝试将array1传递给这个函数:
void function1(int **a);
你会得到一个警告(应用程序将无法正确访问数组):
warning: passing argument 1 of ‘function1’ from incompatible pointer type
因为2D数组与int **不同。可以这么说,将数组自动分解为指针只需要“一层深度”。你需要将函数声明为:
void function2(int a[][2]);
or
void function2(int a[3][2]);
让一切都快乐。
同样的概念可以扩展到n维数组。但是,在应用程序中利用这种有趣的事情通常只会使它更难理解。所以在外面要小心。
答案是基于这样一个想法,即C并没有真正的2D数组——它只有数组的数组。当你宣布:
int someNumbers[4][2];
您要求someNumbers是一个包含4个元素的数组,其中该数组的每个元素的类型为int[2](它本身是一个包含2个int的数组)。
这个难题的另一部分是数组总是在内存中连续地布局。如果你问:
sometype_t array[4];
那么它总是这样的:
| sometype_t | sometype_t | sometype_t | sometype_t |
(4个sometype_t对象彼此相邻,中间没有空格)。在你的someNumbers数组中,它看起来是这样的:
| int [2] | int [2] | int [2] | int [2] |
每个int[2]元素本身就是一个数组,它看起来像这样:
| int | int |
总的来说,你得到了这个:
| int | int | int | int | int | int | int | int |