在C语言中声明一个n指针

3
在C语言中,我想声明一个n维数组,就像下面这个三维数组:
printf("please insert n1, n2, n3\n");
scanf("%d %d %d", &n1, &n2, &n3);

int ***nda;
nda = (int ***)malloc(n1*sizeof(int **));

for (i = 0; i < n1; i++) {
    nda[i] = malloc(n2*sizeof(int *));
    for (j = 0; j < n2; j++) {
        nda[i][j] = malloc(n3*sizeof(int));
    }
}

我希望使用宏来自动化这个过程并扩展到n维,我发现我需要声明一个指针,就像这样:
type *...*(n times)typename;  

我猜宏似乎是实现这个的一种可能方法,但在这里搜索答案后,我发现宏不能递归地展开。
有没有其他方法来在C中实现这个功能?


2
由于代码已经编译,运行时typename有什么用处? - Jiminion
10
有些事情告诉我,这里的问题并不是声明一个指向n级间接寻址的指针。相反,问题在于你为什么要首先这样做。 - WhozCraig
3
@user2690457,这看起来确实像一个XY问题 - user529758
1
谢谢你的快速回复,我将给出我想要做的整个情况。 - user2690457
为什么你想要宏?你可以用 nsizeof object 作为参数声明一个函数,并使其分配/初始化所有东西。 - snf
显示剩余2条评论
3个回答

3

立即停止你目前的操作!每个指针间接引用都会对程序性能造成显著影响。你正在创建的不是多维数组,而是一个N叉树,其中每个数组元素都是指向许多分支的指针,这些分支再次展开。

如果你想创建一个n维数据数组,请使用以下方法:

size_t dim[...]

int *nda;
nda = malloc(sizeof(int) * dim[0] * dim[1] * … * dim[n]);

为了访问数组中的元素,请使用以下方法:
nda[ dim[0]*(i[1] + dim[1]*(i[2] + dim[…]*(i[…+1])) + i[0] ];

@Will:只有到一定程度,才能避免深入阅读语法上的复杂性。你不能在作用域内将VLA强制转换;你必须编写类似于void foo(size_t dim_a, size_t dim_b, elementtype data[dim_a][dim_b])这样的代码,以便将malloc分配的内存区域转换为VLA形状。这并不是非常灵活,而且正如你所说的那样,“复杂性”并不难处理。它是惯用语,并且一旦你做了足够多次,你就可以“跳过”它,而不需要解析每个子语句(就像你不会逐个字符地阅读这些行一样)。 - datenwolf
@Will:嗯,还有一个问题是使用C99特性(我作为C99的粉丝写这篇文章,只需查看我的项目源代码即可),不幸的是,一些编译器仍然不完全支持它(MSVC我在看着你)。 - datenwolf

1
尽管这样做是一个坏主意,但可以做到,所以这里有一种方法。
实际上,如果您使用适当强大的元编程库(如Order,如上所述,Boost是另一个可能的候选项),则可以在预处理器中循环并定义递归宏。Order允许您以熟悉Scheme或ML的函数式风格进行编程。
要循环,请使用for_each结构。要简单地创建给定数量的内容,可以使用for_each_in_range1,N+1
ORDER_PP(    // within this block Order code runs
    8for_each_in_range(8fn(8_, 8print( (*) ) ),
                       1, 8)
)

上面的代码将打印出7个星号。您可以将元编程块包装在常规宏中,这些宏遵循普通的预处理器规则:
// print COUNT stars
#define STARS(COUNT) ORDER_PP( \
    8for_each_in_range(8fn(8_, 8print((*)) ), 1, 8plus(COUNT, 1)) \
)

ORDER_PP块中,假定所有的内容都是Order代码而不是C预处理器代码,这意味着只能调用已识别的Order函数(所有的值/预处理标记必须是原始整数或用8(val)结构进行“引用”)。为了将stars定义为一个Order函数而不是CPP宏,以便可以在嵌套表达式中从ORDER_PP中调用它,我们需要像这样编写它:

#define ORDER_PP_DEF_8stars ORDER_PP_FN( \
    8fn(8C, 8for_each_in_range(8fn(8_, 8print((*)) ), 1, 8plus(8C, 1)) ))

ORDER_PP( 8stars(7) )   // prints 7 stars

Order可以完全透明地提供递归,因此编写嵌套的初始化程序循环相对简单:

#define ORDER_PP_DEF_8ndim_init ORDER_PP_FN( \
    8fn(8N, 8T, 8C, 8I, 8D, \
        8do( \
            8print( 8N (=malloc) 8lparen 8seq_head(8D) (*sizeof) 8lparen 8T 8stars(8minus(8C, 1)) 8rparen 8rparen (;) ), \
            8if(8equal(8C, 1), \
                8print(((void)0;)), \
                8do( \
                    8print( (for) 8lparen (int) 8I (=0;) 8I (<) 8seq_head(8D) (;) 8I (++) 8rparen ({) ), \
                    8ndim_init(8adjoin(8N, 8([), 8I, 8(])), 8T, 8minus(8C, 1), 8cat(8I, 8(_K)), 8seq_tail(8D)), \
                    8print( (}) ) \
                )))))

像这样调用ndim_init

// print the nested initializer from the question
ORDER_PP(
    8ndim_init(8(nda), 8(int), 3, 8(i), 8seq(8(n1), 8(n2), 8(n3)))
)

请注意,C变量名(ndai等)在出现在ORDER_PP块中时需要加引号,以便Order将它们视为文本,而不是尝试评估它们。最后一个参数是一个运行时变量列表,包含每个维度的大小(8seq构建一个列表,8再次引用C变量名)。
您可以将ndim_init的调用打包到一个常规预处理器宏中,以便轻松访问,就像第一个示例中的STARS一样;您可以通过这种方式轻松地将其与声明宏结合起来,在单个调用中发出声明和初始化:
#define NDIM(NAME, TYPE, ...) ORDER_PP ( \
    8lets( (8D, 8((__VA_ARGS__))) \
           (8C, 8tuple_size(8D)), \
        8do( \
            8print( (TYPE) 8stars(8C) (NAME; {) ), \
            8ndim_init(8(NAME), 8(TYPE), 8C, 8(_ITER), 8tuple_to_seq(8D)), \
            8print( (}) ) \
        )) \
)

NDIM(nda, int, n1, n2, n3)   // emits declaration and init block for int ***nda

更多订单示例


如果上面的内容看起来一点也不直观...这就是为什么人们说你不应该这样做。(如果你觉得很简单,那太好了,但其他人可能无法阅读你的代码。)


0
这是一个使用可变长度数组的解决方案(需要C99或更新版本)。在这种情况下,由于其潜在大小,VLA不会分配在堆栈上,而是手动使用(m/c)alloc()进行分配:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_DIM_C 10
#define MAX_DIM_SIZE 100

// Works for versions of C from C99 onward.

int main(void) {
    int dim_c;
    do {
        printf("Please input the number of dimensions. "
               "The minimum value is 1. The maximum value is %d.\n",
               MAX_DIM_C);
    } while (scanf("%d", &dim_c) != 1 || dim_c < 1 || dim_c > 100);
    int dim[MAX_DIM_C];
    // Give all dimensions a default size of 1.
    memset(dim, 1, MAX_DIM_C);
    for (int i = 0; i < dim_c; i++) {
        do {
            printf("Please input the size of dimension %d. "
                   "The minimum value is 1. The maximum value is %d.\n",
                   i, MAX_DIM_SIZE);
        } while (scanf("%d", dim + i) != 1 || dim[i] < 1 || dim[i] > 100);
    }
    // Always allocate a MAX_DIM_C-dimensional array. When the user specifies
    // a number of dimensions fewer than MAX_DIM_C, the MAX_DIM_C-dim_c
    // dimensions are basically just dummy dimensions of size 1
    int (*vla)[dim[1]]
              [dim[2]]
              [dim[3]]
              [dim[4]]
              [dim[5]]
              [dim[6]]
              [dim[7]]
              [dim[8]]
              [dim[9]] =
        calloc(dim[0] *
               dim[1] *
               dim[2] *
               dim[3] *
               dim[4] *
               dim[5] *
               dim[6] *
               dim[7] *
               dim[8] *
               dim[9], sizeof(int));
    //
    // Do something useful here
    //
    printf("%d\n", vla[dim[0] - 1]
                      [dim[1] - 1]
                      [dim[2] - 1]
                      [dim[3] - 1]
                      [dim[4] - 1]
                      [dim[5] - 1]
                      [dim[6] - 1]
                      [dim[7] - 1]
                      [dim[8] - 1]
                      [dim[9] - 1]);
    // To pass the VLA to another function cast it to void (or another simple
    // type) and also pass the dim array along with it to generate a new
    // VLA pointer in the called function with matching dimensions
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接