多维数组如何在内存中格式化?

在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编译器在编译时将其排序?

88801 次浏览

静态二维数组看起来像数组的数组——它只是在内存中连续地布局。数组与指针不是一回事,但因为它们经常可以互换使用,所以有时会令人困惑。不过,编译器会正确地跟踪,这使得一切都很好地排列起来。你必须小心你提到的静态2D数组,因为如果你试图将一个数组传递给一个带有int **参数的函数,就会发生糟糕的事情。这里有一个简单的例子:

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]);

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

让一切都快乐。

同样的概念扩展到__abc0维数组。但是,在应用程序中利用这种有趣的事情通常只会使它更难理解。所以在外面要小心。

回答你的also:两者都有,尽管编译器做了大部分繁重的工作。

在静态分配数组的情况下,“系统”将是编译器。它将像为任何堆栈变量一样保留内存。

在malloc数组的情况下,“系统”将是malloc的实现者(通常是内核)。编译器只分配基指针。

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

答案是基于这样的想法,即C并不是真正的 2D数组——它有数组的数组。当你宣布:

int someNumbers[4][2];

你要求someNumbers是一个包含4个元素的数组,其中数组的每个元素都是int [2]类型(它本身是一个包含2个__abc2的数组)。

这个难题的另一部分是数组总是在内存中连续地布局。如果你问:

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  |
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};

要访问一个特定的2D数组,请考虑数组声明的内存映射,如下所示:

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

要访问每个元素,只需将感兴趣的数组作为参数传递给函数就足够了。然后使用offset for column分别访问每个元素。

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\n",a);
printf("%d\n",b);
}


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

假设,我们有a1a2定义和初始化如下(c99):

int a1[2][2] = \{\{142,143}, {144,145}};
int **a2 = (int* []){ (int []){242,243}, (int []){244,245} };

a1是一个在内存中具有普通连续布局的同构2D数组,表达式(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

编译器知道,当a2的访问表达式操作类型int**时,a1的访问表达式操作类型int[2][2]。生成的程序集代码将遵循同构或异构访问语义。

当类型为int[N][M]的数组被类型强制转换,然后被类型为int**访问时,代码通常会在运行时崩溃,例如:

((int**)a1)[1][0]   //crash on dereference of a value of type 'int'