1

我正在尝试在结构中初始化二维数组,但总是出现错误:

gcc -g -Wall -W -I/usr/include/SDL   -c -o fractal.o fractal.c
In file included from fractal.c:2:0:
fractal.h:12:12: error: array type has incomplete element type ‘double[]’
     double values[][];

这是代码:

struct fractal {

    char name[64];
    int height;
    int width;
    double a;
    double b;
    double meanValue;       
    double values[][];  /*This line is causing the error*/
 };

理想情况下,我想像这样初始化二维数组的高度和宽度:

struct fractal {

    /*... Same code as above ...*/      

    double values[width][height];  
 };

但是在编译时我得到了另外两个错误:

gcc -g -Wall -W -I/usr/include/SDL   -c -o fractal.o fractal.c
In file included from fractal.c:2:0:
fractal.h:12:19: error: ‘width’ undeclared here (not in a function)
     double values[width][height];
                   ^
fractal.h:12:26: error: ‘height’ undeclared here (not in a function)
     double values[width][height];
                          ^

我到处看了看,但我的代码应该可以工作,但我不知道为什么不能。

谢谢您的帮助

4

3 回答 3

3

作为免责声明,这是一个高级主题,因此,如果您是初学者,您可能希望完全放弃它,只需使用一个double*数组,然后为每个指针调用 malloc。(对初学者很好,在专业代码中是不可接受的。)

这是一个高级主题,因为这种特殊情况是 C 语言中的一个弱点。您尝试使用的功能,在结构的末尾有一个空数组,称为灵活数组成员。然而,这只适用于一维。如果在编译时这两个维度都是未知的,您必须想出一个解决方法。

分配部分与任何灵活的数组成员一样:动态分配结构并为尾随数组确定大小。

fractal_t* f = malloc(sizeof *f + sizeof(double[height][width]) );

(在这种情况下,利用方便的 VLA 语法,尽管灵活的数组成员不是 VLA。)

从技术上讲,结构的最后一个成员应该是double[]现在,或者说结构声明。但是 malloc 返回的内存在您访问它之前没有实际的有效类型,之后该内存的有效类型将成为用于访问的类型。

double[][]即使结构中的指针类型不同,我们也可以使用此规则来访问该内存,就像它是 a 一样。给定一个分形f,通过指针访问的代码变成这样:

double (*array_2D)[width] = (double(*)[width]) f->values;

array_2D数组指针在哪里。在这里使用的最正确的类型应该是指向双精度数组的数组指针double (*)[height][width],但是这种类型带有强制性的丑陋访问(*array_2D)[i][j]。为了避免这种丑陋,一个常见的技巧是在数组指针声明中省略最左边的维度,然后我们可以访问它,因为array_2D[i][j]它看起来更漂亮。

示例代码:

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

typedef struct 
{
  char name[64];
  size_t height;
  size_t width;
  double a;
  double b;
  double meanValue;       
  double values[];
} fractal_t;


fractal_t* fractal_create (size_t height, size_t width)
{
  // using calloc since it conveniently fills everything with zeroes
  fractal_t* f = calloc(1, sizeof *f + sizeof(double[height][width]) );
  f->height = height;
  f->width = width;
  // ...
  return f;
}

void fractal_destroy (fractal_t* f)
{
  free(f);
}

void fractal_fill (fractal_t* f)
{
  double (*array_2D)[f->width] = (double(*)[f->width]) f->values;

  for(size_t height=0; height < f->height; height++)
  {
    for(size_t width=0; width < f->width; width++)
    {
      array_2D[height][width] = (double)width; // whatever value that makes sense
    }
  }
}

void fractal_print (const fractal_t* f)
{
  double (*array_2D)[f->width] = (double(*)[f->width]) f->values;

  for(size_t height=0; height < f->height; height++)
  {
    for(size_t width=0; width < f->width; width++)
    {
      printf("%.5f ", array_2D[height][width]); 
    }
    printf("\n");
  }
}

int main (void)
{
  int h = 3;
  int w = 4;

  fractal_t* fractal = fractal_create(h, w);
  fractal_fill(fractal); // fill with some garbage value
  fractal_print(fractal);
  fractal_destroy(fractal);
}
于 2018-04-17T14:02:03.347 回答
2

动态维度数组并不是 C 语言最好的地方……简单可变长度数组仅在 C99 版本的语言中引入,在 C11 版本中成为可选。它们在 MSVC 2017 中仍然不被接受......

但是在这里,您试图在结构中设置一个。这根本不受支持,因为结构必须具有恒定大小(*)(如何处理结构数组)。所以我很抱歉,但这段代码不应该工作,我不知道用 C 语言表达它。

一种常见的方法是用指针替换 2D 动态数组,将指针分配给 2D 数组然后使用它,但即使这样也不是很简单。

你必须以不同的方式设计你的结构......


(*)结构的最后一个元素可能是不完整的类型,例如int tab[];. 这是一个危险的特性,因为程序员有责任为其提供空间。但无论如何,你不能构建一个不完整类型的数组。

于 2018-04-17T13:59:53.503 回答
1

我在设计一个结构以在我的 ODE 求解器中同时保存域值(N x 1 向量)和解值(N x M 矩阵)时遇到了这个问题,以简化函数接口。N 和 M 依赖于模拟,因此是先验未知的。我通过使用GNU Scientific Library的向量矩阵模块解决了这个问题。我发现它比将 FAM(尽管分配为 2D)转换为独立的整个数组指针更简化。

在为结构分配内存之后,我们需要做的就是调用gsl_matrix_alloc()为矩阵保留空间。完成后,调用gsl_matrix_free()将销毁它。请注意,这些函数依赖于数据类型,如文档中所述。

文件名: struct_mat.c

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

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_statistics.h>

typedef struct _fractal {
    char name[64];
    size_t height;
    size_t width;
    double a;
    double b;
    double meanValue;
    gsl_matrix *values;
 } fractal;

fractal * fractal_create(size_t height, size_t width) {
    fractal * fractalObj = (fractal *) malloc(sizeof(fractal));
    fractalObj -> values = gsl_matrix_alloc(height, width);

    if (fractalObj == NULL || fractalObj -> values == NULL) {
        fprintf(stderr, "NULL pointer returned while allocating fractal object.. Exiting program.\n");
        exit(EXIT_FAILURE);
    }

    fractalObj -> height = height;
    fractalObj -> width = width;
    fractalObj -> meanValue = 0.0;

    return fractalObj;
}

void fractal_populate(fractal * fractalObj) {

    srand(time(NULL));
    double current_value = 0.0;

    for (size_t r = 0; r < fractalObj -> height; ++r) {
        for (size_t c = 0; c < fractalObj -> width; ++c) {
            current_value = (double) rand() / (double) RAND_MAX;
            gsl_matrix_set(fractalObj -> values, r, c, current_value);
        }
    }
}

void fractal_calcMeanValue(fractal * fractalObj) {

    gsl_vector_view colVec;

    for (size_t col = 0; col < fractalObj -> values -> size2; ++col) {
        colVec = gsl_matrix_column(fractalObj -> values, col);
        fractalObj -> meanValue += gsl_stats_mean(colVec.vector.data, colVec.vector.stride, colVec.vector.size);
    }

    fractalObj -> meanValue /= fractalObj -> values -> size2;

    printf("\nThe mean value of the entire matrix is %lf\n", fractalObj -> meanValue);

}

void fractal_display(fractal * fractalObj) {
    printf("\n");
    for (size_t r = 0; r < fractalObj -> height; ++r) {
        for (size_t c = 0; c < fractalObj -> width; ++c) {
            printf("%lf ", gsl_matrix_get(fractalObj -> values, r, c));
        }
        printf("\n");
    }
}

void fractal_delete(fractal * fractalObj) {
    gsl_matrix_free(fractalObj -> values);
    free(fractalObj);
}

int main(int argc, char const *argv[]){

    // Program takes number of rows and columns as command line parameters
    switch(argc) {
        case 3:
            printf("Running program..\n"); // to avoid the declaration-succeeding-label error

            size_t height = atoi(argv[1]);
            size_t width = atoi(argv[2]);

            fractal * myFractal = fractal_create(height, width);

            fractal_populate(myFractal);
            fractal_display(myFractal);
            fractal_calcMeanValue(myFractal);

            fractal_delete(myFractal);
            return 0;

        default:
            fprintf(stderr, "USAGE: struct_mat <rows> <columns>\n");
            return 1;
    }

}


通过链接 GSL 和 GSL CBLAS 库进行编译:

gcc -std=c99 struct_mat.c -o struct_mat -lgsl -lgslcblas -lm  

您可以通过您的发行版的包管理器、Windows 上的 Cygwin 或通过编译来安装GSL

在我有限的经验中,使用标准数据结构被证明比使用 FAM 或指向一维数组的指针数组要容易得多。但是,需要注意的是,我们必须记住在分配结构本身之后为矩阵分配内存。

于 2020-07-01T17:58:21.940 回答