Freaky way of allocating two-dimensional array?

In a project, somebody pushed this line:

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

Which supposedly creates a two-dimensional array of (n+1)*(n+1) doubles.

Supposedly, I say, because so far, nobody I asked could tell me what this does, exactly, nor where it originated from or why it should work (which allegedly, it does, but I'm not yet buying it).

Perhaps I'm missing something obvious, but I'd appreciate it if somebody could explain above line to me. Because personally, I'd feel much better if we'd use something we actually understand.

7004 次浏览

这是动态分配2D 数组的典型方法。

  • e is an array pointer to an array of type double [n+1].
  • 因此,sizeof(*e)给出了指向类型的类型,即一个 double [n+1]数组的大小。
  • n+1这样的数组分配空间。
  • 将数组指针 e设置为指向此数组数组中的第一个数组。
  • 这允许您使用 e作为 e[i][j]来访问2D 数组中的各个项。

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

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

The variable e is a pointer to an array of n + 1 elements of type double.

Using the dereference operator on e gives you the base-type of e which is " array of n + 1 elements of type double".

malloc调用只是获取 e的基类型(如上所述)并得到它的大小,将其乘以 n + 1,然后将该大小传递给 malloc函数。实质上是分配 doublen + 1元素的 n + 1阵列数组。

This idiom falls naturally out of 1D array allocation. Let's start with allocating a 1D array of some arbitrary type T:

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

很简单,对吧?表情 *p具有 T类型,因此 sizeof *p给出的结果与 sizeof (T)相同,所以我们为 TN元素数组分配了足够的空间。这对于 any type T来说是正确的。

现在,让我们用类似于 R [10]的数组类型替换 T

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

这里的语义是 T *2作为1D 分配方法; 所有更改的都是 p的类型。不是 T *,现在是 R (*)[10]。表达 *p具有 T型,即 R [10]型,所以 sizeof *p等价于 sizeof (T)sizeof (R [10])等价于 sizeof *p。因此,我们分配足够的空间 NT *0元素数组的 T *1。

假设 R本身是一个数组类型 int [5],那么我们可以进一步讨论这个问题。用 R代替它,我们得到

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

Same deal - sizeof *p is the same as sizeof (int [10][5]), and we wind up allocating a contiguous chunk of memory large enough to hold a N by 10 by 5 array of int.

这是分配端,那么访问端呢?

请记住,就指针算术而言,[]下标操作是 defined: 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

现在,让我们再次执行替换操作,并用数组类型 R [10]替换 T:

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 p0(在这种情况下,p1 arr[0])。由于一元 *的优先级低于下标 []操作符,因此我们必须使用括号来显式地将 p*分组。但是请记住,从上面的 *p是相同的 p[0],所以我们可以用

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

或者只是

R x = p[0][i];

因此,如果 p指向一个2D 数组,我们可以像下面这样通过 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 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. 它很简单——只有一行代码,而不是分段分配方法 < pre > < code > T * * arr = malloc (sizeof * arr * N) ; 如果(arr) { (size _ t i = 0; i < N; i + +) { Arr [ i ] = malloc (sizeof * arr [ i ] * M) ; } }
  2. 所分配数组的所有行都是 * 连续的 * ,这与上面的零碎分配方法不同;
  3. 只需调用一次 free,就可以轻松地重新分配数组。同样,对于零碎分配方法也是不正确的,您必须先释放每个 arr[i],然后才能释放 arr

有时,分段分配方法更可取,比如当堆严重碎片化,无法以连续块的形式分配内存时,或者希望分配一个“锯齿”数组,其中每一行可以有不同的长度。但总的来说,这是更好的办法。


1. 请记住数组 不是指针-相反,数组 表情会根据需要转换为指针表达式。