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


110

在一个项目中,有人推这条线:

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

据推测会创建一个(n + 1)*(n + 1)个double的二维数组。

按说,我说,因为到目前为止,没有人问我能告诉我这是什么呢,正好,也不在那里它源自或者为什么它应该工作(据称,这样做,但我还没有买的话)。

也许我缺少明显的东西,但是如果有人可以向我解释以上内容,我将不胜感激。因为就我个人而言,如果我们使用实际理解的东西会感觉好很多。


15
即使有效,也请发射推杆。
马丁·詹姆斯

22
@MartinJames为什么?否则,您将如何在相邻内存中分配2D数组?变形的一维数组?那是1990年代的编程,现在有了VLA。
隆丁

43
作为记录,这动态分配实际2D数组的唯一方法。
Quentin

15
@Kninnug不,那会声明一个指针数组,而不是2D数组。如果需要2D数组,则没有理由要分配指针数组。由于堆碎片和较慢的缓存利用率,它很慢;由于不能将它用作数组(与memcpy等配合使用),因此它是不安全的。再加上代码is肿。您将需要多个free()调用,这很容易导致内存泄漏。像这样的代码可能广泛传播,这显然是不好的。
伦丁

15
这个问题本来更清晰的解释/回答都试样没有使用相同的价值尺度n+1,而是double (*e)[rows] = malloc(columns * sizeof *e);
恢复莫妮卡- chux

Answers:


87

该变量e是指向n + 1类型为的元素数组的指针double

使用dereference运算符可以e使您的基本类型e为“ n + 1元素类型数组double”。

malloc调用仅采用e(上面说明的)基本类型并获取其大小,然后乘以n + 1,然后将该大小传递给malloc函数。本质上是分配元素数组的n + 1数组。n + 1double


3
@MartinJames sizeof(*e)等效于sizeof(double [n + 1])。将其乘以,n + 1您将得到足够的。
一些程序员哥们2016年

24
@MartinJames:怎么了?并不是那么麻烦,它可以保证分配的行是连续的,并且您可以像其他任何2D数组一样对其进行索引。我在自己的代码中经常使用这个习惯用法。
John Bode

3
看起来似乎很明显,但这仅适用于正方形阵列(相同尺寸)。
詹斯

18
@Jens:仅在n+1两个维度上都输入时,结果将为正方形。如果这样做double (*e)[cols] = malloc(rows * sizeof(*e));,结果将具有您指定的任意数量的行和列。
user2357112支持Monica

9
@ user2357112现在,我宁愿看到。即使这意味着您必须添加int rows = n+1int cols = n+1。上帝将我们从聪明的代码中拯救出来。
candied_orange '16

56

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

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

我个人认为这种样式更容易阅读:

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

12
不错的答案,但我不同意您建议的样式,而是喜欢这种 ptr = malloc(sizeof *ptr * count)样式。
chux-恢复莫妮卡

好的答案,我喜欢您喜欢的风格。可能有一点改进,指出您需要以这种方式进行操作,因为在行之间可能需要考虑填充。(至少,我认为这就是您需要这样做的原因。)(让我知道我是否做错了。)
davidbak

2
@davidbak是同一回事。数组语法仅仅是自记录代码:它与源代码本身一起显示“为2D数组分配空间”。
伦丁

1
@davidbak注:A小调缺点 评论 malloc(row*col*sizeof(double))时出现row*col*sizeof()溢出,但不是sizeof()*row*col没有。(例如row,col are int
chux-恢复Monica

7
@davidbak:sizeof *e * (n+1)更易于维护;如果您决定更改基本类型(例如,从doublelong double),则只需更改e; 的声明。您无需修改调用中的sizeof表达式malloc(这样可以节省时间,并且可以避免在一个位置更改它,而不必在另一个位置更改它)。 sizeof *e将始终为您提供合适的尺寸。
约翰·博德

39

这个习惯用法自然属于一维数组分配。让我们开始分配一个任意类型的一维数组T

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

简单吧?该表达式 *p具有类型T,因此sizeof *p给出的结果与相同sizeof (T),因此我们为的N-element数组分配了足够的空间T任何类型T都是如此。

现在,让我们T用类似的数组类型代替R [10]。然后我们的分配变成

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

这里的语义与一维分配方法完全相同。所有更改的只是的类型p。取而代之的T *是现在R (*)[10]。表达*p具有类型T是类型R [10],因此sizeof *p等效于sizeof (T)这相当于sizeof (R [10])。因此,我们为的N10元素数组分配了足够的空间R

如果需要,我们可以更进一步。假设R本身就是数组类型int [5]。替换为R,我们得到

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

相同的处理- sizeof *p与相同sizeof (int [10][5]),我们最终分配了一个连续的内存块,该内存块足以容纳Nby的10by by 5数组int

这就是分配方面;那么访问端呢?

请记住,[]下标操作定义在指针运算方面: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

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

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 指向的内容(在本例中为array 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

得出与上述相同的结论,并代Rint [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];

如果指向常规数组,或者指向通过分配的内存,则其工作原理相同pmalloc

这种习语具有以下优点:

  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. 只需调用即可取消分配数组的操作free。同样,对于零碎分配方法,这是不正确的,在这种方法中,必须先分配每个分配的对象,arr[i]然后才能分配arr

有时,最好采用零散分配方法,例如,当堆严重碎片化并且无法将内存分配为连续的块时,或者要分配“锯齿状”数组,其中每行的长度可以不同。但总的来说,这是更好的方法。


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


4
+1我喜欢您提出概念的方式:可以为任何类型分配一系列元素,即使这些元素本身是数组。
logo_writer 2016年

1
您的解释确实很好,但是请注意,直到您真正需要连续内存才有意义。连续内存比非连续内存更昂贵。对于简单的2D数组,您的内存布局没有区别(除了用于分配和取消分配的行数之外),因此更喜欢使用非连续的内存。
Oleg Lokshyn
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.