C 如何处理动态分配的任意维数组?

C 如何处理动态分配的任意维数组?,c,arrays,multidimensional-array,C,Arrays,Multidimensional Array,典型的一维数组可以在声明中静态或自动分配 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*a

典型的一维数组可以在声明中静态或自动分配

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;
但是,当您添加第二个维度时,需要一些努力才能实现相同的语法

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;
如果将括号集构造为一组,则只能得到一组双括号

int**arr2l=calloc(n,sizeof*arr2l);

对于(int j=0;j,在j语言(APL的一种方言)的实现中使用了一种数据结构,它容纳动态分配的任意维数组。它使用一种混合的
struct
和一种动态数组作为其数据结构,这是一种通常称为struct hack的技巧。(有关j实现的更多信息和。)

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

struct vec { int n; int p[]; };
由于
p
成员是最后一个,而C没有内置的边界检查,因此它可以用来访问
struct
末尾的额外内存。当然,在分配时,我们需要提供额外的内存,而不是简单地分配
struct
的大小。
struct
只是数组的头y、 C90要求p[]数组的长度有一个数字(比如1),但C99允许省略该数字,因此头的大小更容易计算

因此,一个维度更多的数组需要更多的值来容纳每个维度的大小。为了使我们的结构能够容纳不同维度的数组,这个维度向量也需要变长

我们能做的就是对自身递归地应用struct hack两次。这给了我们一个这样的内存布局,
R
是我们称之为数组秩的维度数,
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向量
D
R
元素之后。因此
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;
}
记住使用单个索引访问二维数据(比如[m][n]个元素的数组)的公式(这是问题中的典型动态数组)。元素[i][j]位于i×n&plus;j。对于三维数组[m][n][o],元素[i][j][k]位于i×n×o&plus;k

因此,我们可以从指数数组和维度数组中计算线性布局数据的单个指数

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];
}
此代码的进一步细化以支持2D矩阵乘法和列切片已发布到中


.

如果将
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");
    }
}
#包括
#包括
#定义n7
int(*a)[N][N];
int main(){
int i,j,k;
a=malloc(N*N*N*sizeof(int));

对于(i=0;i无需重新发明轮子,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]);

此材料是在中的优秀研究员的帮助下编辑的。请不要使用
alloca
,这属于历史书。这在“堆栈”上分配,因此与可变长度数组(VLA)相比,使用它没有任何优势这是标准化的方法。@JensGustedt好的。我已经删除了它。我不想让任何人知道。@JensGustedt:VLA从C11开始是可选的。可能有一个C实现支持
alloca
,但不支持VLA。(这并不一定意味着我不同意你的观点。)请注意,VLA和alloca都有一个缺陷,即它们不报告分配失败。@KeithThompson,是的,但我不知道没有VLA的C11的任何实现,我怀疑永远不会有一个。嗯。但是3D是什么样子的?我想分配甚至看起来更简单。例如
malloc(sizeof(int[n][n][n])
.Hmm.这毕竟还不错。我认为封装大小对于编写在这些数组上运行的函数很有用,但我的示例没有说明这一点。+1向我展示了一些新的东西。因为C99和C11具有灵活的数组成员,所以你不需要像以前那样使用struct hack。是的。删除t会更清楚吗他
1
你认为呢?我在
main
中使用C99复合文字,但我认为如果与C90兼容,其他代码可能会更有用。这取决于你想在多大程度上向那些不得不在Windows上使用Microsoft编译器的人低头。MSVC不支持C99,更不用说C11了,但它基本上不支持
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]);