如何使用动态分配的任意维数组?

时间:2015-05-04 06:30:34

标签: c arrays multidimensional-array

典型的1-D数组可以在声明中静态或自动分配。

enum { n=100 };
int arr1[n];

或通过指针动态分配和访问。

int *arr1m=malloc(n*sizeof*arr1m);
int *arr1c=calloc(n, sizeof*arr1c);

这两种样式都使用相同的语法访问元素。

int i = n/2;
arr1[i] = arr1c[i] = arr1m[i] = 42;

但是当你添加第二个维度时,需要花费一些力气来实现相同的语法。

int arr2[n][n];
int *arr2c=calloc(n*n,sizeof*arr2c);
arr2[5][5] = arr2c[5*n+5] = 23;

如果您将其构造为Iliffe-vector,则只能获得双重括号。

int **arr2l=calloc(n,sizeof*arr2l);
for (int j=0; j<n; j++)
    arr2l[j]=calloc(n,sizeof**arr2l);
 arr2[6][6] = arr2l[6][6] = 72;

但随着尺寸的增加,这变得越来越麻烦。

另一个难点是在访问元素之前检查动态数组的边界(这样您就不会触及未正确分配的内存)。 真实的数组可以使用sizeof运算符来确定边界,但这些动态数组都没有随附它们的大小。

如何定义具有快速,连续布局的结构,如数组,但具有一致的语法,用于访问具有索引列表的元素,这些索引对于2D阵列和3D阵列的工作方式相同;和动态,动态,大小可用,以便它们可以传递给函数并从函数返回?

3 个答案:

答案 0 :(得分:8)

没有必要重新发明轮子,C从C99开始,它被称为可变长度阵列,VLA。它只是语法为“普通”d维数组,只是边界可能是变量的,并且在文件范围内不允许使用它们。

由于此类对象可能变得相对较大,因此不应在堆栈上分配它们,而应使用malloc

之类的对象。
double (*A)[n][m] = malloc(sizeof(double[k][n][m]));

编译器可以帮助您完成所有索引计算而不会出现问题。如果你想将这些动物传递给函数,你必须先小心地声明边界:

void func(size_t k, size_t n, size_t m, double A[k][n][m]);

这使得您的意图对人类读者和编译者都清楚。我比同等形式更喜欢这个

void func(size_t k, size_t n, size_t m, double (*A)[n][m]);

答案 1 :(得分:2)

在J语言(APL的一种方言)的实现中使用了一种数据结构,它适应动态分配的任意维数组。它使用struct和动态数组的混合来实现其数据结构,这种技巧通常称为 struct hack 。 (有关J实施的更多信息herehere。)

要在简单的上下文中看到这个想法,请考虑一维情况:我们需要一个动态的一维数组,它随之携带大小。所以:

struct vec { int n; int p[]; };

由于p成员是最后一个,并且C没有内置边界检查,因此可以使用它来访问struct的最后的附加内存。当然,在分配时,我们需要提供额外的内存而不是简单地分配struct的大小。 struct只是数组的标题。 C90需要一个数字(比如1)作为p []数组的长度,但是C99允许省略数字,因此标题的大小更容易计算。

因此,具有更多维度的数组将需要更多值来保存每个维度的大小。对于我们的结构来容纳不同维度的数组,这个维度向量也需要是可变长度的。

我们可以做的就是将 struct hack 两次应用于递归自身。这给了我们这样的内存布局,其中R是我们称之为数组的 rank 的维数,D值是每个的长度维度,V值是实际的数组数据:

  1   R                    Product(D) 
 --- -------------------- ----------------------------- 
  R  D[0] D[1] ... D[R-1] V[0] V[1] ... V[Product(D)-1] 

并在C中描述,

typedef struct arr { int r; int d[]; } *arr;

数组a的元素紧跟着dims向量R的{​​{1}}元素。因此,D元素可以在V访问(在将索引向量减少到展平表示上的单个索引之后)。这些元素最容易以行主顺序处理。实际元素的数量是所有维度的乘积,所有维度相乘。 编辑:此处的表达式可能写得更好:a->d[r+0], a->d[r+1], ... a->d[r+i]

为了分配这些东西中的一个,我们需要一个函数来计算这个产品作为尺寸计算的一部分。

(a->d+a->r)[0], (a->d+a->r)[1], ... (a->d+a->r)[i]

要初始化,我们只需要填写成员。

int productdims(int rank, int *dims){
    int z=1;
    for(int i=0; i<rank; i++)
        z *= dims[i];
    return z;
}

记住使用单个索引访问2D数据(例如[m] [n]个元素的数组)的公式(它是问题中的典型动态数组)。元素[i] [j]位于 i×n + j 。对于3D阵列[m] [n] [o],元素[i] [j] [k]在 i×(n×o)+ j×o + k

因此,我们可以从索引数组和维数组计算线性布局数据的单个索引。

arr makearr(int rank, int *dims){
    arr z = calloc( (sizeof(struct arr)/sizeof(int)) + 
                rank + productdims(rank,dims), sizeof(int));
    z->r = rank;
    memmove(z->d,dims,rank*sizeof(int));
    return z;
}

不要忘记标题:

int *elem(arr a, ...){
    va_list ap;
    int idx = 0;

    va_start(ap,a);
    if (a->r){
        idx = va_arg(ap,int);
        for(int i=1; i<a->r; i++){
            idx *= a->d[i];
            idx += va_arg(ap,int);
        }
    }
    va_end(ap);

    return &a->d[a->r + idx];
}

瞧瞧:

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

输出:

int main() {    

    {
        int i,n=6;
        arr m = makearr(1, (int[]){n});
        for (i=0;i<n;i++)
            *elem(m,i) = i;
        for (i=0;i<n;i++,printf(" "))
            printf("%d",*elem(m,i));
    }
    puts("\n");

    {
        int i,j,n=4;
        arr m = makearr(2, (int[]){n,n});
        for (i=0;i<n;i++)
            for (j=0;j<n;j++)
                *elem(m,i,j) = i*n+j;
        for (i=0;i<n;i++,printf("\n"))
            for (j=0;j<n;j++,printf(" "))
                printf("%d",*elem(m,i,j));
    }
    puts("\n");

    {
        int i,j,k,n=3;
        arr m = makearr(3, (int[]){n,n,n});
        for (i=0;i<n;i++)
            for (j=0;j<n;j++)
                for (k=0;k<n;k++)
                    *elem(m,i,j,k) = (i*n+j)*n+k;
        for (i=0;i<n;i++,printf("\n"))
            for (j=0;j<n;j++,printf("\n"))
                for (k=0;k<n;k++,printf(" "))
                    printf("%d",*elem(m,i,j,k));
    }

    return 0;
}

此代码的进一步详细说明已支持二维矩阵乘法和列切片,已发布到comp.lang.c中的this thread

Better-motivated question/more powerful data structure

答案 2 :(得分:2)

如果将a定义为指向n整数数组的指针,编译器将执行索引算法。

#define N 7
int (*a)[N];

int main() {
  a = malloc(N*N*sizeof(int));
  a[2][3] = 0;
}

增加:

同样,一个三维的例子:

#include <stdio.h>
#include <stdlib.h>

#define N 7

int (*a)[N][N];

int main() {
    int i,j,k;
    a = malloc(N*N*N*sizeof(int));
    for(i=0; i<N; i++) {
        for(j=0;j<N;j++) {
            for(k=0;k<N;k++) {
                a[i][j][k] = (i*10+j)*10+k;
            }
        }
    }
    for(i=0; i<N; i++) {
        for(j=0;j<N;j++) {
            for(k=0;k<N;k++) {
                printf("%2d ", a[i][j][k]);
            }
            printf("\n");
        }
        printf("\n");
    }
}