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

时间:2010-04-02 04:46:43

标签: c arrays memory data-structures stack-memory

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

...是否在堆栈上创建了类似的结构,还是完全是另一种形式? (即它是指针的一维数组?如果没有,它是什么,以及如何弄清楚它的引用?)

另外,当我说“系统”时,实际负责的是什么呢?内核?或者C编译器在编译时对其进行排序吗?

6 个答案:

答案 0 :(得分:123)

静态二维数组看起来像一个数组数组 - 它只是在内存中连续布局。数组与指针不同,但因为你可以经常互换地使用它们,有时会让人感到困惑。然而,编译器会正确地跟踪,这使得一切都很好地排列。你必须要小心你提到的静态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]);

让一切快乐。

同样的概念扩展到 n - 维数组。但是,在您的应用程序中利用这种有趣的业务通常只会让人更难理解。所以要小心。

答案 1 :(得分:75)

答案是基于C并不真正拥有 2D阵列的想法 - 它有阵列数组。当你声明这个:

int someNumbers[4][2];

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

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

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  |

答案 2 :(得分:26)

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

答案 3 :(得分:5)

在回答你的同时:两者,尽管编译器正在完成大部分繁重工作。

对于静态分配的数组,“The System”将是编译器。它将像任何堆栈变量一样保留内存。

对于malloc'd数组,“The System”将是malloc的实现者(通常是内核)。所有编译器将分配的是基指针。

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

答案 4 :(得分:2)

假设我们已经定义并初始化了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]将从144数组中提取值a1
  • 表达式a2[1][0]将从244数组中提取值a2

a1的访问表达式在类型int[2][2]上运行时,编译器知道a2的访问表达式在类型int**上运行。生成的汇编代码将遵循同构或异构访问语义。

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

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

答案 5 :(得分:1)

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

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

要访问每个元素,只需将您感兴趣的数组作为参数传递给函数即可。然后使用offset of 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;
}