Перемещение массива меньших структур в массив больших структур в C

Сегодня я работал над проблемой перемещения массива меньших структур непосредственно в массив больших структур (arrayNew) (по сути, обновляя меньшие структуры для хранения большего количества информации). Меньшие структуры должны были быть прочитаны с жесткого диска за одну одну операцию чтения в массив новых «обновленных» больших структур, для выполнения «обновления» вызывалась функция. Также всем новым полям в структурах, считанных с жесткого диска, будет присвоено значение '0'. Другими более простыми решениями, которые я пробовал, были:

  • Создание локального массива старых структур (arrayOld), загрузка в него структур с жесткого диска, затем простой цикл по пустому массиву новых структур (arrayNew) и ручное перемещение содержимого каждой структуры из arrayOld в arrayNew. (например, arrayNew[i].x = arrayOld[i].x;) Проблема с этим заключается в том, что в моем случае массивы, с которыми я работал, были очень большими и слишком большими для стека (около 1 МБ для каждого массива), что вызывало ошибку сегментации в момент вызова функции обновления.

  • Другим жизнеспособным решением было создать динамический массив старых структур (arrayDy) и загрузить старые структуры в arrayDy, а затем снова вручную переместить содержимое каждой структуры из arrayDy в arrayNew. (например, arrayNew[i].y = arrayDy[i].y; ) Это решило проблему нехватки памяти стека.

После реализации второго решения. Я решил поэкспериментировать и разработать решение, которое не использует динамически выделяемую память и загружает массив старых структур из HHD непосредственно в больший массив более крупных структур arrayNew за одну операцию чтения и манипулирует содержимым arrayNew в памяти, чтобы дополнить недостающие. значения, которые существуют из-за того, что массив больше.

Я опубликую свое решение ниже в уменьшенной версии того, что я реализовал, используя следующие структуры для моего примера:

typedef struct INNER_STRUCT_ {

    int i_item1;
    int i_item2;
    char i_item3;

} INNER_STRUCT;

typedef struct SMALL_STRUCT_ {

    int item1;
    char item2;
    INNER_STRUCT item3;

} SMALL_STRUCT;

typedef struct BIG_STRUCT_ {

    int item1;
    char item2;
    INNER_STRUCT item3;
    INNER_STRUCT item4;

} BIG_STRUCT;

person The_Neo    schedule 14.10.2014    source источник
comment
Вы загружаете эти 100 структур с диска одним вызовом или 100 вызовами?   -  person alk    schedule 14.10.2014
comment
Если вам не хватает места в стеке, проверьте выравнивание ваших переменных и сначала выделите наиболее ограничивающие. Вы, космические расчеты, предполагаете, что упаковываете структуры   -  person Tim Child    schedule 14.10.2014
comment
Если вы ограничены в пространстве стека, почему бы вам не сохранить массив в другом месте (например, используя динамическое выделение памяти)?   -  person kraskevich    schedule 14.10.2014
comment
Чисто в C вам придется выполнять каждую операцию копирования отдельно (т.е. повторять ее 100 раз). В зависимости от вашего процессора некоторые (например, DSP) имеют специальные операции для этой цели. Но это, конечно, не является частью стандарта языка C.   -  person barak manos    schedule 14.10.2014
comment
функция, которая загружает структуру с диска, вызывается один раз и выдает ошибку, если загрузка не удалась. В настоящее время я работаю над решением с использованием динамической памяти, но рассматривал возможность другого варианта. @TimChild, мне нужно где-нибудь прочитать о выравнивании переменных? Спасибо   -  person The_Neo    schedule 14.10.2014
comment
Это вопрос сериализации на/десериализации с диска, поэтому без предоставления подпрограмм, выполняющих эти две операции, будет трудно ответить. Типичным решением будет изменение процедуры десериализации или создание новой процедуры для создания массива structB_ вместо массива structA_.   -  person midor    schedule 14.10.2014
comment
мои извинения, я не написал вопрос так ясно, как мог бы, но я нашел решение и опубликовал ответ   -  person The_Neo    schedule 16.10.2014


Ответы (2)


Да, это возможно - вы можете использовать для этого union. Стандарт C99 дает специальную гарантию, которую можно использовать для реализации вашего требования:

6.5.2.3-5: Одна специальная гарантия делается для того, чтобы упростить использование объединений: если объединение содержит несколько структур, которые имеют общую начальную последовательность (см. ниже), и если объект объединения в настоящее время содержит одну из этих структур, он разрешено проверять общую начальную часть любого из них в любом месте, где видно объявление полного типа объединения.

Ваши structA_ и structB_ действительно имеют общую начальную последовательность, поэтому создание union и доступ к структурам через него помогут:

union {
    structA a;
    structB b;
} u;
memset(&u.b, 0, sizeof(structB)); // Zero out the bigger structB
loadFromHdd(&u.a); // Load structA part into the union
// At this point, u.b is valid, with its structA portion filled in
// and structB part zeroed out.

Обратите внимание, что вы не можете сделать это с массивом (если, конечно, вы не создадите массив из unions). Каждый structA должен быть загружен отдельно в union, из которого он затем может быть прочитан как structB.

person Sergey Kalinichenko    schedule 14.10.2014
comment
Как это возможно, если данные на жестком диске сжаты в виде массива меньших структур, а данные в ОЗУ необходимо расширить, чтобы за каждой меньшей структурой следовали 20 байтов дополнительных данных? - person barak manos; 14.10.2014
comment
@barakmanos Я добавил примечание, чтобы упомянуть, что это невозможно сделать с массивом. Я предполагаю, что OP может читать struct один за другим. - person Sergey Kalinichenko; 14.10.2014
comment
Хорошо, но я почти уверен, что OP уже знает, как это сделать, с union или без него (что в любом случае даст такую ​​​​же производительность во время выполнения). - person barak manos; 14.10.2014

Метод, который я предлагаю и использую в качестве решения, в основном загружает меньшие структуры для жесткого диска (в данном случае файл) в массив новых больших структур, а затем перестраивает блок памяти, чтобы к каждому полю можно было получить доступ правильно. Код, иллюстрирующий это, приведен ниже и представляет собой mcve.

#include <stdio.h>
#include <string.h>

typedef struct INNER_STRUCT_ {

    int i_item1;
    int i_item2;
    char i_item3;

} INNER_STRUCT;

typedef struct SMALL_STRUCT_ {

    int item1;
    char item2;
    INNER_STRUCT item3;

} SMALL_STRUCT;

typedef struct BIG_STRUCT_ {

    int item1;
    char item2;
    INNER_STRUCT item3;
    INNER_STRUCT item4;
    /* 
    Note that the big struct is exactly the same as the small 
    struct with one extra field - Key to this method working 
    is the fact that the extension to the struct is appended
    at the end, in an array of the structs will be placed one 
    after the other in memory with no gaps*/

} BIG_STRUCT;

void printSmallStruct (SMALL_STRUCT *inStruct, int count) {
    // Print everything inside given small struct
    printf("\n\n Small struct %d, item1: %d \n",count,inStruct->item1);
    printf(" Small struct %d, item2: %c \n",count,inStruct->item2);
    printf(" Small struct %d, item3.i_item1: %d \n",count,inStruct->item3.i_item1);
    printf(" Small struct %d, item3.i_item2: %d \n",count,inStruct->item3.i_item2);
    printf(" Small struct %d, item3.i_item3: %c \n",count,inStruct->item3.i_item3);
}

void printBigStruct (BIG_STRUCT *inStruct, int count) {
    // Print everything inside given big struct
    printf("\n\n Big struct %d, item1: %d \n",count,inStruct->item1);
    printf(" Big struct %d, item2: %c \n",count,inStruct->item2);
    printf(" Big struct %d, item3.i_item1: %d \n",count,inStruct->item3.i_item1);
    printf(" Big struct %d, item3.i_item2: %d \n",count,inStruct->item3.i_item2);
    printf(" Big struct %d, item3.i_item3: %c \n",count,inStruct->item3.i_item3);
    printf(" Big struct %d, item4.i_item1: %d \n",count,inStruct->item4.i_item1);
    printf(" Big struct %d, item4.i_item1: %d \n",count,inStruct->item4.i_item2);
    printf(" Big struct %d, item4.i_item1: %c \n",count,inStruct->item4.i_item3);
}

int main() {


    SMALL_STRUCT smallStructArray[5];       // The array of small structs that we will write to a file then read

    BIG_STRUCT   loadedBigStructArray[5];   // The large array of structs that we will read the data from the file into

    int i;  // Counter that we will use

    FILE *pfile;    // pointer to our file stream

    void *secondary_ptr;    // void pointer that we will use to 'chop' memory into the size we want

    /* Fill the array of structs (smallStructArray) */
    for (i = 0; i < 5; i++) {
    /* We fill each field with different data do we can ID that the right data is in the right fields */
        smallStructArray[i].item1 = 111;
        smallStructArray[i].item2 = 'S';
        INNER_STRUCT*    temp = &smallStructArray[i].item3;
        temp->i_item1 = 777;
        temp->i_item2 = 999;
        temp->i_item3 = 'I';
    }


    /* Write the contents of smallStructArray to binary file then display it */
    pfile = fopen("test.dat","wb");
    if (pfile!=NULL){
    for (i = 0; i < 5; i++) {
        fwrite(&smallStructArray[i],sizeof(SMALL_STRUCT),1,pfile);
    }
    fclose(pfile);
    }
    else{
    printf("Unable to open file!");
    return 1;
    }

    for (i = 0; i < 5; i++) {
         printSmallStruct(&smallStructArray[i],i);
    }

    /* Clear array of big structs using memset  */
    memset(&loadedBigStructArray[0],0,sizeof(loadedBigStructArray));

    /* Here we read from the smallStructArray that was aved to file into the  loadedBigStructArray */
    pfile = fopen("test.dat","rb");
    if (pfile !=NULL){
    /*
    He we pass fread the following:     size_t fread(void *args1, size_t args2, size_t args3, FILE *args4)
    args1   - a pointer to the beginning of a block of memory, in our case the beginning of the 
          array loadedBigStructArray.

    args2   - the size of the ammout of bytes we wish to read, in our case the size of a SMALL_STRUCT, 
          the size one of the elments in the array saved to the file.

    args3   - the ammount of elements to read, in our case five (which is the number of elements the 
          array saved to the file has. 

    args4   - a pointer to a FILE that specifies our input stream.

    Essentially what fread will do here is read a block of bytes the size of the array we saved to 
    the file (smallStructArray) into the array in memory loadedBigStructArray from the 
    beggining of loadedBigStructArray. Fig 1 illustrates what this will look like in memory.
    */
    fread(&loadedBigStructArray,sizeof(SMALL_STRUCT),5,pfile);
    fclose(pfile);
    }
    else{
    printf("Unable to open file!");
    return 1;
    }
    /* 
    Due to the way the array on the file has been read into the array in memory, if we try 
    to access the data in loadedBigStructArray only the first 5 values will be valid, due to 
    the  memory not being in the order we want. We need to re-arrange the data in loadedBigStructArray
    */

    /* 
    Here we use a void pointer to point to  the beggining of the loadedBigStructArray.
    we will use this pointer to 'chop' the data loadedBigStructArray into SMALL_STRUCT 
    sized 'chunks' we can read from.

    Due to the way pointers and arrays work in C we can cast the void pointer to any type we want
    and get a chunk of memory that size begginnig from the pointer and its off set.
    E.g. : int temp = ((int *)void_ptr)[i];  
    This example above will give us an integer 'temp' that was taken from memory beggining from position
    void_ptr in memory and its offset i. ((int *)void_ptr) casts the pointer to type int and [i] dereferances
    the pointer to location i.
    */
    secondary_ptr = &loadedBigStructArray;

    /* 
    Not we are going through the array backwards so that we can rearange the data with out overwriting 
    data in a location that has data which we havent moved yet. As the bottom end of the loadedBigStructArray
    is essentially empty we can shift data down that way.
    */
    for (i = 5; i > -1; i=i-1) {


    SMALL_STRUCT temp = ((SMALL_STRUCT *)secondary_ptr)[i]; // dereference pointer to SMALL_STRUCT [i] inside loadedBigStructArray call it 'temp'

    /*
    Now that we have dereferenced a pointer a given SMALL_STRUCT inside loadedBigStructArray called 'temp'
    we can use temp to move the data inside temp to its corresponding position in loadedBigStructArray 
    which rearragnes the data.
    */
    loadedBigStructArray[i].item1 = temp.item1;
    loadedBigStructArray[i].item2 = temp.item2;
    loadedBigStructArray[i].item3.i_item1 = temp.item3.i_item1;
    loadedBigStructArray[i].item3.i_item2 = temp.item3.i_item2;
    loadedBigStructArray[i].item3.i_item3 = temp.item3.i_item3;

    /* We then fill the new field to be blank */
    loadedBigStructArray[i].item4.i_item1 = 0;
    loadedBigStructArray[i].item4.i_item2 = 0;
    loadedBigStructArray[i].item4.i_item3 = '0';
    }

    /* Print our new structures */
    for (i = 0; i < 5; i++) {
         printBigStruct(&loadedBigStructArray[i],i);
    }

    return 0;
}

Визуализация техники:

Визуализация техники

Когда fread выполняет единственную операцию чтения массива, сохраненного на диске, в массив в памяти из-за того, что он меньше, он займет первое зелье массива в памяти, но «нижний» раздел может быть чем угодно, если мы попытаемся получить доступ данных в новом массиве с текущими дескрипторами, которые у нас есть на данных, мы либо получим неверную информацию, либо плохой кусок памяти. Мы должны переупорядочить эти данные, прежде чем сможем использовать какие-либо из наших дескрипторов для структур в массиве.

person The_Neo    schedule 16.10.2014
comment
пожалуйста, прокомментируйте, если что-то не имеет смысла - person The_Neo; 16.10.2014