Оптимизация структуры массивов в C - PullRequest
2 голосов
/ 15 января 2020

Я пытаюсь написать структуру для хранения координат для различных графических c элементов, а также некоторых функций set и get. Значения координат будут меняться для размещения альтернативных шрифтов всякий раз, когда пользователь меняет свой выбор языка. Однако число наборов координат, которые мне нужны (целые числа, которые мне нужно сохранить), будет постоянным и определяется как numElements в структуре.

Пожалуйста, рассмотрите следующий код. Это своего рода библиотека, которую я создал для поддержки других функций рисования graphi c, которые я использую. Он работает как есть, но у меня встроенные системы, поэтому память стоит дорого, и я хотел бы внести изменения, но не знаю, как заставить это работать.

#define MAX_ELEM 20

typedef enum{TITLE, HEADER, TEXT, LAST_GRAPHIC_ITEM}GRAPHIC_ITEMS;

typedef struct
{
     uint16_t x;
     uint16_t y;
}COORDINATES;

typedef struct
{
    const uint16_t numElements;
    COORDINATES coord[MAX_ELEM];
}GRAPHIC_COORD;

GRAPHIC_COORD graphicItemCoordinates[LAST_GRAPHIC_ITEM] =
{
    { // TITLE
        1,
    },
    { // HEADER
        2,
    },
    { // TEXT
        14,
    }
};

void SetXCoord(GRAPHIC_ITEMS item, uint16_t new_x, uint16_t elemNum);
void SetYCoord(GRAPHIC_ITEMS item, uint16_t new_y, uint16_t elemNum);
uint16_t GetXCoord(GRAPHIC_ITEMS item, uint16_t elemNum);
uint16_t GetYCoord(GRAPHIC_ITEMS item, uint16_t elemNum);

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

typedef struct
{
    const uint16_t numElements;
    COORDINATES coord[numElements]; // changed size value here from MAX_ELEM to numElements
}GRAPHIC_COORD;

Однако, когда я делаю это изменение и пытаюсь скомпилировать, я получаю ошибки, говорящие о том, что numElements не определен. Мне кажется, что мне не нужно было определять numElements в объявлении типа, и этого достаточно для определения его в экземпляре структуры, которую я объявляю. Но очевидно, что это не так. Я предполагаю, что мне нужно что-то сделать с динамическим распределением памяти c, но я еще глубоко не затронул эту тему и не уверен, что мне нужно делать.

Примечание: приведенный выше код значительно упрощен. Часть моей цели в том, чтобы сделать это таким образом, чтобы избежать большой структуры, которую мне нужно поддерживать. В реальном приложении более 30 графических элементов c, каждый из которых содержит 10-20 элементов. Если бы я сделал это, как предложили @Clifford или @Bastien, то у меня было бы более 1000 строк файла структуры / заголовка.

Примечание: numElements существует только для определения размера координатного массива в структуре GRAPHIC_COORD. Если бы я сделал это так, как предлагают @Clifford или @Bastien, я мог бы полностью опустить член numElements, что упростило бы структуру. Но структура все равно будет нелепо длинной.

Я работаю в IDE Keil uVision5 с микросхемой STM32F103 на специальной плате, если это имеет какое-либо значение.

Ответы [ 3 ]

4 голосов
/ 15 января 2020

Dynami c Распределение памяти и массивы переменной длины, как правило, не рекомендуется на устройствах с ограниченным объемом памяти, так как в случае сбоя выделения вы должны иметь возможность с ним справиться, и в этом случае, вероятно, нет ничего полезного, что вы могли бы делать.

Если для каждого элемента длина координатного массива фиксирована, то динамическое распределение c совершенно не требуется в любом случае. Рассмотрим:

typedef struct
{
    const uint16_t numElements;
    COORDINATES* coord ;  // <<< pointer to coordinates array
}GRAPHIC_COORD;

Затем:

COORDINATES title_coord[]  = { /*constant initialiser coords*/ } ;
COORDINATES header_coord[] = { /*constant initialiser coords*/ } ;
COORDINATES text_coord[]   = { /*constant initialiser coords*/ } ;

Затем, наконец:

GRAPHIC_COORD graphicItemCoordinates[] =
{
    { sizeof(title_coord)  / sizeof(GRAPHIC_COORD), title_coord  },
    { sizeof(header_coord) / sizeof(GRAPHIC_COORD), header_coord },
    { sizeof(text_coord)   / sizeof(GRAPHIC_COORD), text_coord   }
};

#define LAST_GRAPHIC_ITEM (sizeof(graphicItemCoordinates) / sizeof(*graphicItemCoordinates))

Каждый массив COORDINATES может быть разным (хотя и индивидуально исправленным) длина.

Вы можете упростить инициализаторы, используя макросы, например:

#define ITEM_COORDS_INIT( item ) {sizeof(item) / sizeof(GRAPHIC_COORD), (item)}

Тогда:

GRAPHIC_COORD graphicItemCoordinates[] =
{
    ITEM_COORDS_INIT(title_coord),
    ITEM_COORDS_INIT(header_coord),
    ITEM_COORDS_INIT(text_coord)
};

Обратите внимание, что зачастую лучше разрешить размер массивы будут определяться их инициализатором, а не помещать значение в []. Вы можете создать символ для числа элементов, создав макрос, как я описал выше для LAST_GRAPHIC_ITEM.

. Обратите внимание, что если вы намереваетесь поместить эти структуры в ПЗУ, чего у вас больше, чем RAM обычно, тогда вы должны объявить их static const:

static const COORDINATES title_coord[]  = { /*constant initialiser coords*/ } ;
...
static const GRAPHIC_COORD graphicItemCoordinates[] = ...
2 голосов
/ 15 января 2020

1.

Вы правы, использование динамического c выделения памяти, безусловно, является опцией.

сначала вы должны использовать указатель вместо массив фиксированного размера в вашей структуре:

typedef struct
{
    const uint16_t numElements;
    COORDINATES * coords;
}GRAPHIC_COORD;

Затем вы можете создать следующую функцию для динамического распределения координат:

void alloc_graphic_coords(GRAPHIC_COORD * graphic_coords, size_t graphic_coords_size)
{
    size_t i;

    for(i = 0; i < graphic_coords_size; i++)
    {
        /* can return null pointer if no memory available (should be handled) */
        graphic_coords[i].coords = (COORDINATES *) calloc(graphic_coords[i].numElements, sizeof(COORDINATES));
    }
}

, затем вы можете использовать ее следующим образом:

#define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]))
// ...
alloc_graphic_coords(graphicItemCoordinates, ARRAY_SIZE(graphicItemCoordinates));

В какой-то момент выделенная память должна быть освобождена.

Будьте осторожны при использовании динамического c выделения памяти. это может привести к утечке памяти, если не обрабатывается правильно. Вы обязательно должны ознакомиться с ним, прежде чем внедрять его в свой проект.

Кроме того, проверьте размер вашей кучи памяти в настройках Keil IDE (должен быть достаточно большим, чтобы в нем могли храниться координаты).

2.

Если вы не хотите выполнять динамическое выделение памяти c, вы можете сделать что-то вроде этого:

#define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]))

typedef enum{
    TITLE = 0,
    HEADER,
    LAST_GRAPHIC_ITEM
}GRAPHIC_ITEMS;


typedef struct
{
     uint16_t x;
     uint16_t y;
}COORDINATES;


typedef struct
{
    const uint16_t numElements;
    COORDINATES * coords;
}GRAPHIC_COORD;


COORDINATES title_coordinates[] =
{
    {
        7, 9    // x & y coordinates
    },
};

COORDINATES header_coordinates[] =
{
    {
        5, 10    // x & y coordinates
    },
    {
        2, 3    // x & y coordinates
    }
};

GRAPHIC_COORD graphicItemCoordinates[] =
{
    {
        ARRAY_SIZE(title_coordinates),
        title_coordinates
    },
    {
        ARRAY_SIZE(header_coordinates),
        header_coordinates,
    },
    // ... (add other coordinates here)
};

int main()
{
    printf("1rst x coordinate of title = %d", graphicItemCoordinates[TITLE].coords[0].x);

    return 0;
}

Примечание: Вам не нужно инициализировать координаты. Например, вы можете заменить:

COORDINATES header_coordinates[] =
{
    {
        5, 10    // x & y coordinates
    },
    {
        2, 3    // x & y coordinates
    }
};

на:

#define HEADER_COORDS_SIZE (2)
COORDINATES header_coordinates[HEADER_COORDS_SIZE];
0 голосов
/ 16 января 2020

Спасибо всем за ваш вклад!

Я остановился на решении, которое работает с точки зрения размера и эффективности кода на экране. Это решение гарантирует, что объем хранимых данных будет равен объему, который нам действительно нужен. Это решение также кажется мне читаемым людьми в дополнение к тому, что его легко понять. Это важно, поскольку новые члены команды могут приходить и уходить; чем меньше времени уходит на обучение, тем лучше. Наконец, я считаю, что этот код будет легко масштабируемым в неизбежном случае, когда нужно будет создавать новые элементы graphi c. Ниже приведен этот код. Скоро появится сценарий, который я буду использовать для поддержки полноразмерной версии этого долгосрочного проекта. Ура.

/* Type Declarations */
typedef enum{TITLE, HEADER, TEXT, LAST_GRAPHIC_ITEM}GRAPHIC_ITEMS;

typedef struct
{
     uint16_t x;
     uint16_t y;
}COORDINATES;

typedef struct
{
    const uint16_t numElements;
    COORDINATES* coord;
}GRAPHIC_COORD;

/* Constants */
#define NUM_TITLE_ELEM     1
#define NUM_HEADER_ELEM    2
#define NUM_TEXT_ELEM      14

/* Variables */
COORDINATES titleCoord[NUM_TITLE_ELEM];
COORDINATES headerCoord[NUM_HEADER_ELEM];
COORDINATES textCoord[NUM_TEXT_ELEM];

GRAPHIC_COORD graphicItemCoordinates[LAST_GRAPHIC_ITEM] =
{
    { // TITLE
        NUM_TITLE_ELEM,
        titleCoord
    },
    { // HEADER
        NUM_HEADER_ELEM,
        headerCoord
    },
    { // TEXT
        NUM_TEXT_ELEM,
        textCoord
    }
};

/* Functions */
void SetXCoord(GRAPHIC_ITEMS item, uint16_t new_x, uint16_t elemNum)
{
    graphicItemCoordinates[item].coord[elemNum].x = new_x;
}
void SetYCoord(GRAPHIC_ITEMS item, uint16_t new_y, uint16_t elemNum)
{
    graphicItemCoordinates[item].coord[elemNum].y = new_y;
}
uint16_t GetXCoord(GRAPHIC_ITEMS item, uint16_t elemNum)
{
    return graphicItemCoordinates[item].coord[elemNum].x;
}
uint16_t GetYCoord(GRAPHIC_ITEMS item, uint16_t elemNum)
{
    return graphicItemCoordinates[item].coord[elemNum].y;
}
...