207

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

int** someNumbers = malloc(arrayRows*sizeof(int*));

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

显然,这实际上创建了一个指向一组单独的一维整数数组的指针的一维数组,当我要求时,“系统”可以弄清楚我的意思:

someNumbers[4][2];

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

int someNumbers[ARRAY_ROWS][ARRAY_COLUMNS];

...是否在堆栈上创建了类似的结构,或者它完全是另一种形式?(即它是一维指针数组吗?如果不是,它是什么,以及如何找出对它的引用?)

另外,当我说“系统”时,究竟是什么负责解决这个问题?内核?还是C编译器在编译时对其进行排序?

4

6 回答 6

160

静态二维数组看起来像一个数组数组——它只是在内存中连续布局。数组与指针不同,但因为您经常可以互换使用它们,所以有时会让人感到困惑。但是,编译器会正确跟踪,这使得一切都很好。你必须小心你提到的静态二维数组,因为如果你尝试将一个传递给一个带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

因为二维数组与int **. 可以说,数组自动衰减为指针只会“深一层”。您需要将函数声明为:

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

或者

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

让一切快乐。

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

于 2010-04-02T04:49:58.120 回答
88

答案是基于 C 并没有真正二维数组的想法——它有数组的数组。当您声明这一点时:

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  |
于 2010-04-02T06:24:30.483 回答
29
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};
于 2013-02-01T13:02:18.847 回答
5

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

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

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

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

于 2010-04-02T05:13:27.790 回答
2

假设,我们已经定义a1a2初始化如下(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 数组初始化,并且是指向 type 值的指针int*,即解引用表达式*a2计算为 type 值int*,内存布局不必是连续的:

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

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

  • 表达式将从数组中a1[1][0]获取值144a1
  • 表达式将从数组中a2[1][0]获取值244a2

a1当 for 的访问表达式对 type进行操作时,编译器知道 forint[2][2]的访问表达式对 type 进行a2操作int**。生成的汇编代码将遵循同构或异构访问语义。

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

((int**)a1)[1][0]   //crash on dereference of a value of type 'int'
于 2020-01-11T03:12:35.370 回答
1

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

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

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

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;
}
于 2016-02-09T12:51:13.210 回答