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

Use*_*291 110 c arrays malloc allocation multidimensional-array

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

double (*e)[n+1] = malloc((n+1) * sizeof(*e));
Run Code Online (Sandbox Code Playgroud)

据推测,这会创建一个(n + 1)*(n + 1)双倍的二维数组.

据说,我说,因为到目前为止,我没有人问我能告诉我它的作用,确切地说,它起源于何处或为什么它应该起作用(据称,它确实如此,但我还没有购买它).

也许我错过了一些明显的东西,但如果有人能够向我解释上述内容,我会很感激.因为就个人而言,如果我们使用我们真正理解的东西,我会感觉好多了.

Som*_*ude 87

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

使用dereference运算符e为您提供基类型,e即" n + 1类型元素数组double".

所述malloc呼叫简单地取碱型的e(如上所述),并得到它的大小,通过相乘n + 1,并且该大小传递给malloc函数.基本上分配一个元素数组的n + 1数组.n + 1double

  • @MartinJames:它出了什么问题?它不是那种眼睛,它保证分配的行是连续的,你可以像任何其他2D数组一样索引它.我在自己的代码中经常使用这个习惯用法. (24认同)
  • @Jens:只有在你为两个维度加上'n + 1`的意义上,结果才是正方形.如果你执行`double(*e)[cols] = malloc(rows*sizeof(*e));`,结果将包含你指定的任意数量的行和列. (17认同)
  • @ user2357112现在我更愿意看到.即使这意味着你必须添加`int rows = n + 1`和`int cols = n + 1`.上帝拯救我们脱离聪明的代码. (9认同)
  • @MartinJames`sizeof(*e)`相当于`sizeof(double [n + 1])`.用'n + 1'乘以它就足够了. (3认同)
  • 这似乎很明显,但这仅适用于*square*数组(相同的尺寸). (3认同)
  • @ryyker:你不会使用 `memset` - 如果你想将数组归零(在合理但非标准强制的假设下,所有 0 位的双精度数意味着 0.0),你可以使用 `calloc`,或者显式如果您想要不同的初始化,请循环。如果您已经对数组做了很多工作,并且想要将其重新初始化为零,则可以使用 `memset(e, 0, rows*cols*sizeof(double))` - 或 ` memset(e, 0, rows*sizeof(e))`。 (2认同)

Lun*_*din 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]) );
Run Code Online (Sandbox Code Playgroud)

  • 不错的回答,除了我不同意你建议的风格,更喜欢`ptr = malloc(sizeof*ptr*count)`风格. (12认同)
  • @davidbak:`sizeof*e*(n + 1)`更容易维护; 如果你决定改变基类型(例如从'double`到`long double`),那么你只需要改变`e`的声明; 你不需要在`malloc`调用中修改`sizeof`表达式(这可以节省时间并保护你不在一个地方而不是另一个地方改变它).`sizeof*e`将始终为您提供合适的尺寸. (7认同)
  • @davidbak这是同样的事情.数组语法只是自我记录的代码:它用源代码本身说"为2D数组分配空间". (2认同)
  • @davidbak注意:[comment](http://stackoverflow.com/questions/36794202/freaky-way-of-allocing-2dim-array/36794373#comment61176498_36794360) `malloc(row*col*sizeof(double) 的一个小缺点))` 当 `row*col*sizeof()` 溢出时发生,但 `sizeof()*row*col` 不会溢出。(例如 row,col 是 `int`) (2认同)

Joh*_*ode 39

这个成语自然地脱离了一维数组分配.让我们从分配一些任意类型的一维数组开始T:

T *p = malloc( sizeof *p * N );
Run Code Online (Sandbox Code Playgroud)

简单吧?该表达 *p有型T,所以sizeof *p给出了相同的结果sizeof (T),所以我们要为某个分配足够的空间N的-元素阵列T.这适用于任何类型T.

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

R (*p)[10] = malloc( sizeof *p * N);
Run Code Online (Sandbox Code Playgroud)

这里的语义与1D分配方法完全相同 ; 所有改变的是类型p.而不是T *,它现在R (*)[10].表达*p具有类型T是类型R [10],因此sizeof *p等效于sizeof (T)这相当于sizeof (R [10]).所以我们为Nby 10元素数组分配了足够的空间R.

如果我们想要的话,我们可以更进一步; 假设R它本身就是一个数组类型int [5].替换为R我们获得

int (*p)[10][5] = malloc( sizeof *p * N);
Run Code Online (Sandbox Code Playgroud)

相同的交易 - sizeof *p是相同的sizeof (int [10][5]),我们最终分配一个连续的大块内存,足以容纳一个Nby 10by 5数组int.

那就是分配方面; 接入方面怎么样?

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

T x = *p;
Run Code Online (Sandbox Code Playgroud)

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

T x = p[0]; // identical to *p
Run Code Online (Sandbox Code Playgroud)

因此,如果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
Run Code Online (Sandbox Code Playgroud)

现在,让我们再次执行替换操作并替换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];
Run Code Online (Sandbox Code Playgroud)

一个明显的区别; 我们p在应用下标运算符之前显式解除引用.我们不想下标p,我们要下标到什么p (在这种情况下,数组 arr[0]).由于一元*比标低优先级[]运营商,我们必须使用括号明确组p*.但请记住,从上面*p也是p[0]如此,所以我们可以用它代替

R x = (p[0])[i];
Run Code Online (Sandbox Code Playgroud)

要不就

R x = p[0][i];
Run Code Online (Sandbox Code Playgroud)

因此,如果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
Run Code Online (Sandbox Code Playgroud)

以这种相同的结论如上而代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];
Run Code Online (Sandbox Code Playgroud)

如果指向常规数组,或者它指向通过分配的内存,则它的工作方式相同. 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 );
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 分配的数组的所有行都是*连续的*,而上面的零碎分配方法不是这种情况;
  3. 只需一次调用即可轻松取消分配数组free.同样,对于零碎的分配方法也不是这样,你必须在解除分配arr[i]之前解除分配arr.

有时候,零碎的分配方法是可取的,例如当你的堆碎片非常碎片而且你不能将你的内存分配为一个连续的块时,或者你想要分配一个"锯齿状"的数组,其中每一行可以有不同的长度.但总的来说,这是更好的方法.


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

  • +1我喜欢你提出这个概念的方式:为任何类型分配一系列元素是可能的,即使这些元素本身就是数组. (4认同)