怪异的方式分配二维数组?

在一个项目中,有人推动了这一行:

double (*e)[n+1] = malloc((n+1) * sizeof(*e)); 

据推测,它可以创build(n + 1)*(n + 1)双精度的二维数组。

据说 ,因为到目前为止,我所说的没有人能够告诉我这到底是什么,也不是它起源于什么地方,为什么它应该起作用(据说它确实有,但我还没有买)。

也许我错过了一些显而易见的东西,但是如果有人能够向我解释上述问题,我会很感激。 因为个人而言,如果我们使用我们真正理解的东西,我会感觉好多了。

variablese是指向doubletypes的n + 1元素的数组的指针。

e上使用反引用运算符给出了e的基types,即“ doubletypes的n + 1元素的数组”。

malloc调用只需要e的基types(如上所述)并获取其大小,将其乘以n + 1 ,然后将该大小传递给malloc函数。 基本上分配一个n + 1数组的n + 1数组。

这是您应该dynamic分配二维数组的典型方法。

  • e是一个指向double [n+1]types数组的数组指针。
  • sizeof(*e)因此给出了指向types的types,这是一个double [n+1]数组的大小。
  • 你为n+1这样的数组分配空间。
  • 您将数组指针设置为指向此数组数组中的第一个数组。
  • 这使您可以使用e e[i][j]来访问2D数组中的各个项目。

我个人认为这种风格更容易阅读:

 double (*e)[n+1] = malloc( sizeof(double[n+1][n+1]) ); 

这个成语自然是脱离了一维数组的分配。 让我们从分配一些任意typesT一维数组开始:

 T *p = malloc( sizeof *p * N ); 

很简单,对吧? expression式 *p具有typesT ,所以sizeof *p给出了与sizeof (T)相同的结果,所以我们为TN元素数组分配足够的空间。 对于任何typesT都是如此。

现在,让我们用一个像R [10]这样的数组types来replaceT 那我们的分配就变成了

 R (*p)[10] = malloc( sizeof *p * N); 

这里的语义与1D分配方法完全一样 , 所有改变的是p的types。 现在是R (*)[10] ,而不是T * 。 expression式*ptypesT是typesR [10] ,所以sizeof *p等于sizeof (T) ,它等于sizeof (R [10]) 。 所以我们为R 10元素的数组分配足够的空间。

如果我们愿意,我们可以更进一步。 假设R本身就是一个数组typesint [5] 。 替代R ,我们得到

 int (*p)[10][5] = malloc( sizeof *p * N); 

同样的处理 – sizeof *psizeof (int [10][5]) ,我们结束分配一个足够大的内存块来保存一个N105int数组。

这就是分配方面; 接入方面呢?

请记住, []下标操作是根据指针运算来定义的: a[i]被定义为*(a + i) 1 。 因此,下标运算符[] 隐式地解引用一个指针。 如果p是指向T的指针,则可以通过使用一元*运算符显式取消引用来访问指向的值:

 T x = *p; 

或者使用[]下标运算符:

 T x = p[0]; // identical to *p 

因此,如果p指向数组的第一个元素,则可以使用指针p上的下标来访问该数组的任何元素:

 T arr[N]; T *p = arr; // expression arr "decays" from type T [N] to T * ... T x = p[i]; // access the i'th element of arr through pointer p 

现在,让我们再次做我们的replace操作,并用数组typesR [10]replaceT

 R arr[N][10]; R (*p)[10] = arr; // expression arr "decays" from type R [N][10] to R (*)[10] ... R x = (*p)[i]; 

一个立即明显的差异; 在应用下标运算符之前,我们明确地解引用了p 。 我们不想下标成p ,我们要下标到p 指向的地方 (在这种情况下是数组 arr[0] )。 由于一元*优先级低于下标[]运算符,因此我们必须使用括号将p明确地与* 。 但从上面记得*pp[0]是一样的,所以我们可以用

 R x = (p[0])[i]; 

要不就

 R x = p[0][i]; 

因此,如果p指向一个二维数组,我们可以像这样通过p索引到该数组中:

 R x = p[i][j]; // access the i'th element of arr through pointer p; // each arr[i] is a 10-element array of R 

采取与上述相同的结论,并用int [5]代替R int [5]

 int arr[N][10][5]; int (*p)[10][5]; // expression arr "decays" from type int [N][5][10] to int (*)[10][5] ... int x = p[i][j][k]; 

如果p指向一个常规的数组,或者指向通过malloc分配的内存,这个工作原理是一样的

这个习语有以下好处:

  1. 这很简单 – 只是一行代码,而不是零碎的分配方法
     T **arr = malloc( sizeof *arr * N ); if ( arr ) { for ( size_t i = 0; i < N; i++ ) { arr[i] = malloc( sizeof *arr[i] * M ); } } 
  2. 分配数组的所有行都是*连续的*,这与上面的零碎分配方法不同;
  3. 释放数组对于释放数据同样简单。 再一次,不正确的零碎分配方法,你必须释放每个arr[i]然后才能释放arr

有时零碎的分配方法是可取的,比如当你的堆碎片很差,你不能把你的内存分配成一个连续的块时,或者你想分配一个“锯齿”的数组,每一行的长度可能不一样。 但总的来说,这是更好的方法。


1.请记住,数组不是指针,而是根据需要将数组expression式转换为指针expression式。