Как создать структуру структур известного максимального размера

Правка. Теперь мне известно о путанице между массивом и указателем. Я ценю замечания, но не буду уточнять вопрос, так как он потеряет часть причины, по которой я его написал.

Я пытаюсь инициализировать структуру, состоящую из структур, каждая из которых содержит массив. Более четко, у меня есть эта матричная структура:

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;

Для справки: это определение матрицы и функция инициализации, используемая позже, взяты из библиотеки arm_math в CMSIS. .

Мне трудно понять, как мне создать переменную dataset. После ответов и обсуждения на этой странице вопросов я понимаю Я не могу волшебным образом ожидать, что 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. Действительно ли я гарантирую, что все элементы arm_matrix_instance_f32, содержащиеся в структуре dataset, которую я создаю, имеют достаточно места для всех своих элементов?
  4. Я объявил структуру как содержащую A, B, C. Что произойдет, если я инициализирую их в другом порядке? Например, как структура узнает, сколько места нужно оставить между A и C, если B еще не объявлено?

person raggot    schedule 18.07.2018    source источник
comment
memset(d, 0, sizeof(*d)); не собирается обнулять ваши данные. Используйте для этого calloc   -  person Jean-François Fabre    schedule 18.07.2018
comment
Глядя на определение функции 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 становятся недействительными.   -  person Alex F    schedule 18.07.2018
comment
Однако вам нужно знать API, который вы используете, дайте дополнительную информацию, чтобы получить ответ.   -  person Alex F    schedule 18.07.2018
comment
В вашем struct нет массива. Указатель не является массивом. И это, кажется, какая-то система MCU с голым железом. Как правило, очень плохая идея использовать malloc & co в таких системах.   -  person too honest for this site    schedule 18.07.2018
comment
И это проблема XY в любом случае. Вы используете очень неправильный подход.   -  person too honest for this site    schedule 18.07.2018
comment
@AlexF Из того, что я понял, глядя на функции, функция инициализации просто заполняет поля, как вы догадались. Я не понимаю, однако, почему мой метод будет неверным, потому что вывод, который я передаю, является указателем на структуру dataset, в которой указатели на структуры arm_matrix_instance_f32 не должны теряться. Я ошибся?   -  person raggot    schedule 18.07.2018
comment
@ Олаф, спасибо. Не могли бы вы подробнее рассказать о «проблеме XY»? Что ты имеешь в виду? Если бы вы могли указать мне правильный подход, я буду очень рад!   -  person raggot    schedule 18.07.2018
comment
@raggot: Для правильного подхода мне нужно было знать детали проекта, архитектуру, платформу и т. д. Это далеко не то, для чего предназначен этот сайт. Я бы посоветовал сделать шаг назад и начать с чего-то более простого, чтобы правильно выучить язык и сначала немного попрактиковаться. Не пытайтесь бежать, пока не научитесь ходить. Если это встроенный проект, как я предполагаю, это даже более важно: они гораздо менее терпимы к проблемному / ошибочному коду.   -  person too honest for this site    schedule 18.07.2018
comment
@raggot: все указатели pData становятся недействительными, когда возвращается create_dataset, поскольку они указывают на локальные массивы.   -  person Alex F    schedule 18.07.2018
comment
@АлексФ. Я пропустил это...! Спасибо.   -  person raggot    schedule 18.07.2018


Ответы (3)


Чтобы создать переменную типа 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
}

Почему? Потому что вы не хотите переделывать весь свой код после того, как передумаете (или переключитесь на другое устройство) и решите разместить матрицы в куче. Вы просто изменяете свои функции init и destroy.

person n. 1.8e9-where's-my-share m.    schedule 18.07.2018

Я думаю, вам следует подойти к этому более детально и начать с выделения места для каждого arm_matrix_instance_f32 отдельно. Рассмотрите возможность создания фабричной функции для этих экземпляров. Это создаст более читаемый код и позволит вам просто заменить arm_matrix_instance_f32 в вашем dataset другими экземплярами.

С другой стороны, если вы всегда знаете количество матриц и максимальное количество элементов, которые они содержат, вы можете использовать составные литералы для создания набора данных:

 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,
             }
         },

     };
 }

Это освободит вас от выделения/освобождения кучи.

person pbn    schedule 18.07.2018
comment
Мне нравится этот подход. Немного многословно (на самом деле у меня сейчас 25 матриц, а не 3), но понятно и, кажется, решает все проблемы с управлением памятью за один раз. Я попробую через некоторое время и вернусь к вам. - person raggot; 18.07.2018
comment
@raggot На самом деле у меня сейчас 25 матриц. Вероятно, не очень хорошая идея иметь структуру из 25 элементов одинакового типа. Массив может быть более подходящим. - person n. 1.8e9-where's-my-share m.; 18.07.2018

Осторожно, указатели и массивы — разные животные!

Здесь 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
}
person Serge Ballesta    schedule 18.07.2018
comment
Спасибо за разъяснения. - person raggot; 18.07.2018
comment
Это дает мне представление о том, куда я должен положить руки. Есть идеи, почему ответ был отклонен? Возможно, могут быть более элегантные способы запуска функций инициализации вместо жесткого кодирования размера? - person raggot; 18.07.2018
comment
@raggot: Что касается понижения, я не знаю, но malloc на встроенном устройстве с ограниченной памятью следует использовать очень осторожно, чего не делал мой первоначальный код. - person Serge Ballesta; 18.07.2018