【问题标题】:declare a n-pointer in C在 C 中声明一个 n 指针
【发布时间】:2013-08-16 19:06:05
【问题描述】:

在 C 中,我想声明一个 n 维数组,如下面的 3-dim。

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-dim,我发现我需要声明一个这样的指针:

type *...*(n times)typename;  

我猜宏是一种看似可行的方法,但在这里搜索答案后,我发现宏不会递归扩展。
在 C 中是否有任何解决方法可以做到这一点?

【问题讨论】:

  • 代码既然编译好了,那么运行时的typename有什么用呢?
  • 有些事情告诉我这里的问题不是声明指向 n 级间接的指针。相反,这似乎是您为什么首先要这样做。
  • 对不起,我已经改变了问题。
  • @user2690457 这确实看起来像XY problem
  • 感谢您的快速回复,我会全面说明我想做的事情。

标签: c pointers multidimensional-array c-preprocessor


【解决方案1】:

停止做你现在做的事!每个级别的指针间接都会对您的程序造成重大的性能影响。您在其中创建的不是多维数组,而是 n 叉树,其中每个数组元素都是指向多个分支的指针,这些分支再次展开。

如果你想为 n 维数据创建一个数组,这样做:

size_t dim[...]

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

要寻址数组中的元素,请使用 this

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-ed 内存区域转换为VLA 形状。这不是很灵活,而且你所说的“笨拙”并不是很难工作。这是惯用的,一旦你完成了足够多的时间,你只需“阅读”它,而不用解析每个子语句(就像你不逐个字符地阅读这些行一样)。
  • @Will:嗯,使用 C99 功能也存在问题(我是作为 C99 粉丝写这篇文章的,看看我的项目的源代码)是,不幸的是,一些编译器仍然没有'不完全支持它(MSVC 我在看着你)。
【解决方案2】:

虽然这样做是个坏主意,但它可以做到,所以这是一种方法。

如果您使用像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

More Order examples


如果上面的内容看起来一点都不简单……那就是人们说你不应该这样做的原因。 (如果是这样,对您有好处,但其他人将无法阅读您的代码。)

【讨论】:

    【解决方案3】:

    这是一个可变长度数组的解决方案(需要 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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-05-16
      • 1970-01-01
      • 1970-01-01
      • 2015-05-02
      • 1970-01-01
      • 1970-01-01
      • 2022-09-30
      相关资源
      最近更新 更多