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

Freaky way of allocating two-dimensional array?

提问人:User1291 提问时间:4/22/2016 最后编辑:Peter MortensenUser1291 更新时间:4/27/2016 访问量:7702

问:

在一个项目中,有人推了这样一句话:

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

据推测,它创建了一个 (n+1)*(n+1) 双精度的二维数组。

我说,据说是因为到目前为止,我问过的人都无法告诉我它到底是做什么的,也不知道它来自哪里或为什么它应该起作用(据称,它确实如此,但我还没有买它)。

也许我遗漏了一些明显的东西,但如果有人能向我解释上面的行,我将不胜感激。因为就我个人而言,如果我们使用我们真正理解的东西,我会感觉好得多。

C 维数组 malloc 分配

评论

50赞 Quentin 4/22/2016
郑重声明,这是动态分配实际 2D 阵列的唯一方法。

答:

62赞 Lundin 4/22/2016 #1

这是动态分配 2D 阵列的典型方法。

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

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

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

评论

13赞 chux - Reinstate Monica 4/22/2016
不错的答案,除了我不同意你建议的风格,更喜欢这种风格。ptr = malloc(sizeof *ptr * count)
0赞 davidbak 4/23/2016
很好的答案,我喜欢你喜欢的风格。一个轻微的改进可能是指出您需要这样做,因为需要考虑行之间的填充。(至少,我认为这就是你需要这样做的原因。(如果我错了,请告诉我。
2赞 Lundin 4/23/2016
@davidbak 这是一回事。数组语法只是自记录代码:它说“为 2D 数组分配空间”以及源代码本身。
2赞 chux - Reinstate Monica 4/23/2016
@davidbak 注意:注释的一个小缺点是溢出时会出现,但不会溢出。malloc(row*col*sizeof(double))row*col*sizeof()sizeof()*row*colint)
8赞 John Bode 4/23/2016
@davidbak:更易于维护;如果您决定更改基本类型(例如,从 to ),则只需更改 ;您无需修改调用中的表达式(这样可以节省时间,并防止在一个位置更改表达式,而不需要在另一个位置更改表达式)。 总能给你合适的尺寸。sizeof *e * (n+1)doublelong doubleesizeofmallocsizeof *e
95赞 Some programmer dude 4/22/2016 #2

该变量是指向 类型的元素数组的指针。en + 1double

使用 dereference 运算符 on 为您提供其基本类型为“类型的元素数组”。een + 1double

调用只是获取 的基本类型(如上所述)并获取其大小,将其乘以 ,然后将该大小传递给函数。实质上是分配 的元素数组的数组。mallocen + 1mallocn + 1n + 1double

评论

4赞 Some programmer dude 4/22/2016
@MartinJames 等效于 。乘以它,你就得到了足够的。sizeof(*e)sizeof(double [n + 1])n + 1
26赞 John Bode 4/22/2016
@MartinJames:这是怎么回事?这并不是那么刺眼,它保证分配的行是连续的,你可以像任何其他 2D 数组一样索引它。我在自己的代码中经常使用这个成语。
3赞 Jens 4/23/2016
这似乎很明显,但这仅适用于方形数组(相同维度)。
19赞 user2357112 4/23/2016
@Jens:只有在某种意义上,如果你把两个维度都放进去,结果将是正方形的。如果这样做,结果将具有您指定的行数和列数。n+1double (*e)[cols] = malloc(rows * sizeof(*e));
10赞 candied_orange 4/23/2016
@user2357112 现在我宁愿看到。即使这意味着您必须添加 和 .上帝将我们从聪明的代码中拯救出来。int rows = n+1int cols = n+1
45赞 John Bode 4/23/2016 #3

这个成语自然不属于一维数组分配。让我们从分配某个任意类型的一维数组开始:T

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

很简单,对吧?表达式的类型为 ,因此给出的结果与 相同,因此我们为 的 -element 数组分配了足够的空间。这适用于任何 T*pTsizeof *psizeof (T)NT

现在,让我们用数组类型代替,如 .然后我们的分配变为TR [10]

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

这里的语义与一维分配方法完全相同;所有更改的只是 的类型。而不是 ,而是现在 .表达式的 type 是 类型 ,所以等价于 which 等价于 。因此,我们为 的 by element 数组分配了足够的空间。pT *R (*)[10]*pTR [10]sizeof *psizeof (T)sizeof (R [10])N10R

如果我们愿意,我们可以更进一步;假设本身就是一个数组类型。代之以它,我们得到Rint [5]R

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

同样的交易 - 与 相同,我们最终分配了一个连续的内存块,该内存块足够大,可以按数组保存 by by 的数组。sizeof *psizeof (int [10][5])N105int

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

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

T x = *p;

或使用下标运算符:[]

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

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

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

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

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

一个立即明显的差异;在应用下标运算符之前,我们会显式取消引用。我们不想下标到 ,我们想下标到指向什么(在本例中为数组)。由于一元的优先级低于下标运算符,因此我们必须使用括号来显式地使用 .但请记住,从上面看,这与 相同,因此我们可以将其替换为ppparr[0]*[]p**pp[0]

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

或者只是

R x = p[0][i];

因此,如果指向一个 2D 数组,我们可以通过以下方式索引到该数组中:pp

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. 只需调用 一次即可轻松解除分配数组。同样,零碎分配方法并非如此,在该方法中,您必须先取消分配每个分配,然后才能解除分配。freearr[i]arr

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


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

评论

6赞 logo_writer 4/24/2016
+1 我喜欢你提出这个概念的方式:为任何类型分配一系列元素都是可能的,即使这些元素本身就是数组。
1赞 Oleg Lokshyn 4/27/2016
你的解释真的很好,但请注意,在你真正需要它之前,连续内存的分配并不是一个好处。连续内存比非连续内存更昂贵。对于简单的 2D 数组,内存布局没有区别(除了分配和解除分配的行数),因此最好使用非连续内存。
1赞 CoR 12/12/2020
@John 预示着从函数返回的最佳方式(如果可能的话)。但我想保留 ar[x][y] 表示法。int (*p)[10][5] = malloc( sizeof *p * N);
0赞 John Bode 12/12/2020
@CoR:如果我正确理解了你的问题,你就会回来。函数原型将是 ( 是一个接受参数并返回指向 10x5 数组的指针的函数)。pint (*foo(int N))[10][5]foointNint
0赞 CoR 12/12/2020
@John Bode,我需要避免那个原型。10 和 5 稍后将由用户提供。是否可以使用此表示法创建“返回”数组或指向错误数组的指针或指向指针的指针的 C 函数?int (*foo(int N))[10][5]