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


184

在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编译器在编译时将其整理出来?


8
如果可以的话,我会给+1以上。
罗伯·拉克兰

1
警告:此代码中没有二维数组!
对于这个网站来说太老实了

@toohonestforthissite确实。对此进行扩展:循环和调用malloc()不会产生N维数组。。它导致将指针数组[指向指针数组[...]]完全分开一维数组。请参阅正确分配多维数组,以了解如何分配TRUE N维数组。
Andrew Henle

Answers:


144

静态二维数组看起来像数组的数组-它只是连续排列在内存中。数组与指针不是一回事,但是由于您经常可以互换使用它们,因此有时会造成混淆。但是,编译器会正确跟踪,从而使所有内容都很好地排列在一起。您一定要小心使用您提到的静态2D数组,因为如果您尝试将一个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维数组。但是,在您的应用程序中利用这种有趣的业务通常只会使它变得更难以理解。所以要小心。


感谢您的解释。因此,“ void function2(int a [] [2]);” 将接受静态和动态声明的2D吗?而且我想如果将第一个维保留为[],那么也要传递数组的长度仍然是一种好习惯/基本做法?
克里斯·库珀

1
@Chris我不这么认为-您将很难使C将一个堆栈或全局分配的数组转换为一堆指针。
Carl Norum '04

6
@JasonK。-不 数组不是指针。在某些情况下,数组会“衰减”到指针中,但是它们绝对一样。
卡尔·诺鲁姆

1
需要明确的是:克里斯(Chris)“作为一个单独的参数传递数组的长度仍然是一种好习惯,否则请使用std :: array或std :: vector(这是C ++而不是旧的C)。我认为@CarlNorum在概念上既适合新用户,又几乎可以接受,在Quora上引用Anders Kaseorg:“学习C的第一步是了解指针和数组是同一回事。第二步是了解指针和数组是不同的。”
Jason K.

2
@JasonK。“学习C的第一步是理解指针和数组是同一回事。” -此报价非常错误且具有误导性!理解它们并不相同确实是最重要的步骤,但是对于大多数运算符来说,数组会转换为指向第一个元素的指针!sizeof(int[100]) != sizeof(int *)(除非你找到一个平台100 * sizeof(int)字节/ int,但是这是一个不同的事情。
太老实了这个网站

84

答案是基于这样的想法,即C并不真正具有 2D数组-它具有数组数组。当您声明时:

int someNumbers[4][2];

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

难题的另一部分是,数组始终在内存中连续布置。如果您要求:

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  |

1
看看最终的布局,让我认为可以将int a [] []作为int * ...来访问吗?
Narcisse Doudieu Siewe,2015年

2
@ user3238855:类型不兼容,但是如果您获得了指向int数组数组中第一个的指针(例如,通过求值a[0]&a[0][0]),则可以,可以将其偏移以依次访问每个int)。
CAF

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

5

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

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

对于malloc的数组,“系统”将是malloc(通常是内核)的实现者。编译器将分配的全部是基本指针。

编译器将始终按照声明的类型来处理类型,但在Carl给出的示例中,它可以确定可互换的用法。这就是为什么如果将[] []传递给函数,则必须假定它是静态分配的平面,其中**被认为是指向指针的指针。


@JonL。我不会说malloc是由内核实现的,而是由内核原语(例如brk)上的libc实现的
Manuel Selva

@ManuelSelva:malloc标准未指定在何处以及如何实施,而是由实施自行决定。环境。对于独立的环境,它是可选的,就像标准库的所有部分都需要链接功能一样(这实际上是要求的结果,而不是标准说明的字面意义)。对于某些现代托管环境,它确实依赖于内核功能,或者是完整的东西,或者是您使用stdlib和kernel-primitives编写的(例如Linux)。对于非虚拟内存单进程系统,它只能是stdlib。
对于这个网站来说太老实了

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数组初始化的,并且是一个指向type值的指针int*,即,解除引用表达式*a2求值为type的值int*,内存布局不必是连续的:

a2 --> p1 p2
       ...
p1 --> 242 243
       ...
p2 --> 244 245

尽管内存布局和访问语义完全不同,但是数组访问表达式的C语言语法对于同质和异质2D数组看起来完全相同:

  • 表达a1[1][0]将获取值144出来的a1阵列
  • 表达a2[1][0]将获取值244出来的a2阵列

a1type 的access-expression 操作于type时int[2][2],编译器会知道access-expression的a2操作针对type int**。生成的汇编代码将遵循同构或异构访问语义。

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

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

1

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

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

要访问每个元素,只需将您感兴趣的数组作为参数传递给函数即可。然后对列使用offset来分别访问每个元素。

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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.