【问题标题】:How to create a struct of structs of known maximum size [closed]如何创建已知最大大小的结构结构[关闭]
【发布时间】:2018-07-18 09:04:38
【问题描述】:

编辑:我现在意识到我在数组和指针之间造成的混淆。我很欣赏这些评论,但不会使问题更准确,因为它会失去我写它的部分原因。

我正在尝试初始化一个由结构组成的结构,每个结构都包含一个数组。更清楚地说,我有这个矩阵结构:

typedef struct
{
    uint16_t numRows;     /**< number of rows of the matrix.     */
    uint16_t numCols;     /**< number of columns of the matrix.  */
    float32_t *pData;     /**< points to the data of the matrix. */
} arm_matrix_instance_f32;

我需要将这些矩阵对象的数量放在一个更大的结构中

typedef struct
{
    arm_matrix_instance_f32 A;
    arm_matrix_instance_f32 B;
    arm_matrix_instance_f32 C;
} dataset;

作为参考,这个矩阵定义和后面用到的初始化函数,来自CMSIS中的arm_math库。

我很难理解应该如何创建dataset 变量。根据question page 中的答案和讨论,我知道我不能神奇地期望 C 知道使用诸如dataset d 之类的声明来分配多少内存。

仅根据链接问题的解决方案,我想出了一个函数来为dataset 初始化足够的空间,并使用一个函数来创建dataset 类型的变量。我现在有这样的东西:

dataset* create_dataset( void ) {

    uint8_t n_matrices = 3;
    uint8_t n_elements = 9;
    dataset* d= malloc( n_matrices * (sizeof(float32_t)*n_elements + sizeof(uint16_t)*2));
    memset(d, 0, sizeof(*d));

    const float32_t zeros33_f32[9] =
    {
        0.0, 0.0, 0.0,
        0.0, 0.0, 0.0,
        0.0, 0.0, 0.0,
    };
    const float32_t zeros31_f32[3] =
    {
        0.0,
        0.0,
        0.0,
    };
    const float32_t zeros13_f32[3] =
    {
        0.0, 0.0, 0.0,
    };

    arm_mat_init_f32( &(d->A), 3, 3, (float32_t *)zeros33_f32);
    arm_mat_init_f32( &(d->B), 3, 1, (float32_t *)zeros31_f32);
    arm_mat_init_f32( &(d->C), 1, 3, (float32_t *)zeros13_f32);

    return d;
}

基本上,我假设矩阵的数量和它们包含的最大元素数量都是已知的,因此会保留足够的内存。

我有以下问题:

  1. 这种嵌套结构的整体方法是否正确?
  2. dataset 结构的空间分配是否正确?
  3. 我真的要确保我创建的dataset 结构中包含的所有arm_matrix_instance_f32 元素都有足够的空间容纳它们的所有元素吗?
  4. 我将结构声明为包含A, B, C。如果我以另一个顺序初始化它们会发生什么?例如,如果 B 仍未声明,结构如何知道在 AC 之间留出多少空间?

【问题讨论】:

  • memset(d, 0, sizeof(*d)); 不会将您的数据归零。为此使用calloc
  • 这里看arm_mat_init_f32函数定义:cs.indiana.edu/~bhimebau/CMSIS/Documentation/DSP/html/…不知道像arm_mat_release_f32这样的函数在哪里?如果这样的函数不存在,我可以猜测arm_mat_init_f32 只是填充了arm_matrix_instance_f32 字段并且不进行任何分配。在这种情况下你的代码是错误的,因为create_dataset函数退出后,所有pData指针都失效了。
  • 但是,您需要知道您正在使用的API,提供更多信息以获得答案。
  • 您的struct 中没有数组。指针不是数组。这似乎是一些裸机 MCU 系统。在这样的系统上使用malloc & co 通常是一个非常糟糕的主意。
  • 无论如何,这都是一个 XY 问题。你使用了非常错误的方法。

标签: c memory-management struct arm cmsis


【解决方案1】:

创建dataset(或任何struct)类型的变量:

dataset d;

就是这样。仅此而已。

在堆上分配dataset(或任何struct)类型的对象:

dataset* dp = malloc(sizeof(dataset));

就是这样。仅此而已。

现在初始化这样一个对象是一个不同的问题。但是为了初始化某些东西,你需要先创建那个东西。最好将创建和初始化这两个过程在心理上分开。

所以你手上有一个未初始化的struct。如何初始化它?逐个字段。

每个字段都是一个矩阵,可能需要自己复杂的初始化,所以编写一个专用的矩阵初始化函数是有益的。我们先用一个,以后再写。假设您必须在堆上分配数据集。

dataset* allocate_dataset() {
       dataset* dp = malloc(sizeof(dataset));
       if (dp == NULL) { /* report out-of-memory error */ }
       init_matrix(&dp->A, 3, 3);
       init_matrix(&dp->B, 3, 1);
       init_matrix(&dp->C, 1, 3);
       return dp;
}

在堆上分配的任何东西最终都必须被释放,所以我们编写了一个对称的释放函数:

void free_dataset(dataset* dp) {
       destroy_matrix(&dp->A);
       destroy_matrix(&dp->B);
       destroy_matrix(&dp->C);
       free(dp);
}

开始矩阵初始化。有一个库函数可以做到这一点,但它需要一个指向其数据数组的指针,该指针应该分配在某个地方。我认为它存在于堆上。

void init_matrix(arm_mat_init_f32* mp, int rows, int cols) {
    float32_t* data = malloc(sizeof(float32_t * rows * cols);
    if (data == NULL) { /* report out-of-memory error */ }
    arm_mat_init_f32(mp, rows, cols, data);   
}

销毁矩阵几乎是微不足道的:

void destroy_matrix(arm_mat_init_f32* mp) {
    free (mp->pData);
}

再次假设您需要在堆上分配矩阵数据。情况不一定如此。也许您使用的是内存有限的嵌入式设备。现在让我们假设相反:没有堆。你不需要分配你的数据集,你仍然需要初始化它:

void init_dataset (dataset* dp);

现在 init_matrix 什么都不做,只调用arm_mat_init_f32,所以我们可以直接使用后者:

void init_dataset (dataset* dp) {
    arm_mat_init_f32(&dp->A, 3, 3, (float32_t[]){0,0,0, 0,0,0, 0,0,0});
    arm_mat_init_f32(&dp->B, 3, 1, (float32_t[]){0,0,0});
    arm_mat_init_f32(&dp->C, 1, 3, (float32_t[]){0,0,0});
}

不需要销毁,但您可能仍希望保留不执行任何操作的销毁函数,以防万一,并在适当的地方调用它们。

void destroy_dataset(dataset* dp) { 
    destroy_matrix(&dp->A);
    destroy_matrix(&dp->B);
    destroy_matrix(&dp->C);
}

void destroy_matrix(arm_mat_init_f32* mp) { 
  (void)mp; // suppress compiler warning
}

为什么?因为一旦您改变主意(或切换到不同的设备)并决定在堆上分配矩阵,您不想重做所有代码。您只需修改 initdestroy 函数即可。

【讨论】:

    【解决方案2】:

    我认为您应该对此采取更精细的方法,并从分别为每个 arm_matrix_instance_f32 分配空间开始。考虑为这些实例创建一个工厂函数。它将生成更具可读性的代码,并允许您简单地将 dataset 中的 arm_matrix_instance_f32 替换为其他实例。

    另一方面,如果您始终知道矩阵的数量和它们包含的最大元素数量,则可以使用复合文字来生成数据集:

     dataset create()
     {
         return (dataset) {
             .A = {
                 3, 3, (float32_t [])  {
                     1.0, 2.0, 3.0,
                     4.0, 5.0, 6.0,
                     7.0, 8.0, 9.0,
                 }
             },
             .B = {
                 3, 3, (float32_t [])  {
                     2.0, 0.0, 0.0,
                     0.0, 0.0, 0.0,
                     0.0, 0.0, 0.0,
                 }
             },
             .C = {
                 3, 3, (float32_t [])  {
                     2.0, 0.0, 0.0,
                     0.0, 0.0, 0.0,
                     0.0, 0.0, 0.0,
                 }
             },
    
         };
     }
    

    这将使您从堆分配/释放中解放出来。

    【讨论】:

    • 我喜欢这种方法。有点冗长(我目前实际上有 25 个矩阵,而不是 3 个)但很清晰,似乎可以一次性解决所有内存管理问题。我会尝试一下然后回复你。
    • @raggot “我现在实际上有 25 个矩阵” 拥有 25 个相同类型元素的结构可能不是一个好主意。数组可能更合适。
    【解决方案3】:

    注意,指针和数组是不同的动物!

    这里,arm_matrix_instance_f32 不包含数组,只包含一个指针。 dataset 包含其中 3 个。句号。

    这意味着这行是完全错误的:

    dataset* d= malloc( n_matrices * (sizeof(float32_t)*n_elements + sizeof(uint16_t)*2));
    

    您应该分别分配结构和数组:

    dataset* create_dataset( void ) {
    
        dataset* d = malloc(sizeof(*d));                  // allocate memory for the structs
        if (d == NULL) return NULL;                       // could not allocate
        // allocate memory for the arrays (9 + 3 + 3)
        float32_t *array = malloc(15 +  * sizeof(*array));
        if (array == NULL) {
            free(d);                                      // free d if array not allocated
            return NULL;
        }
    
        for (int i=0; i<15; i++) array[i] = 0.;           // zeroes the arrays
    
        arm_mat_init_f32( &(d->A), 3, 3, array);          // pass 9 first elements to A
        arm_mat_init_f32( &(d->B), 3, 1, array + 9);      // pass following 3 to B
        arm_mat_init_f32( &(d->C), 1, 3, array + 12);     // pass last 3 to C
    
        return d;
    }
    

    由于您已使用 malloc 分配了所有内容,因此您稍后必须释放它:

    void destroy_dataset(dataset *d) {
        free(d->A.pData);      // free the array (A got the beginning of the allocated array)
        free(d);               // and the struct
    }
    

    【讨论】:

    • 感谢您的澄清。
    • 这让我知道我必须把手放在哪里。知道为什么答案被否决了吗?也许有更优雅的方式来运行初始化函数而不是硬编码大小?
    • @raggot:对于downvote,我不知道,但是内存有限的嵌入式设备上的malloc应该非常谨慎地使用,我的初始代码没有。
    猜你喜欢
    • 2010-09-13
    • 2015-11-26
    • 1970-01-01
    • 2011-11-07
    • 2010-09-20
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    相关资源
    最近更新 更多