优化C中数组的结构

Optimizing structure of arrays in C

我正在尝试编写一个结构来存储各种图形元素的坐标以及一些设置和获取函数。每当用户更改他们的语言选择时,坐标值将更改以适应替代字体。但是,我需要的坐标集数量(我需要维护的整数)将保持不变,并在结构中定义为 numElements。

请考虑以下代码。这是我为支持我使用的其他图形绘制功能而创建的一种库。它按原样工作,但我在嵌入式系统中,因此内存非常宝贵,我想进行更改但不知道如何使其工作。

#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,在我声明的结构实例中定义它就足够了。但显然事实并非如此。我假设我需要对动态内存分配做一些事情,但我还没有深入探讨这个主题,也不确定我需要做什么。

注意:我上面提供的代码已经简化了很多。我这样做的部分目标是避免拥有需要维护的大型结构。在实际应用中,大约有 30 个图形项,每个图形项包含 10-20 个元素。如果我按照@Clifford 或@Bastien 的建议去做,那么我将拥有 1000 多行 structure/header 文件。

注意:numElements 仅用于确定 GRAPHIC_COORD 结构中坐标数组的大小。如果我按照@Clifford 或@Bastien 的建议去做,我可以完全省略 numElements 成员,这将简化结构。但是结构仍然会很长。

我正在使用 Keil 的 uVision5 IDE 在定制板上使用 STM32F103 芯片,如果这很重要的话。

1.

你说得对,使用动态内存分配当然是一种选择。

首先,您必须在结构中使用指针而不是固定大小的数组:

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));

分配的内存应该在某个时候释放。

使用动态内存分配时要小心。如果处理不当,这可能会导致内存泄漏。 在你的项目中实施它之前,你一定要熟悉它。

此外,在keil IDE设置中检查堆内存的大小(应该足够大,以便可以在其中存储坐标)。

2.

如果你不想执行动态内存分配,你可以这样做:

#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];

动态内存分配和可变长度数组在内存受限的设备上通常是不明智的,因为当分配失败时,您必须能够处理它,在这种情况下,您可能无能为力.

如果是对于每个元素来说,坐标数组的长度是固定的,那么动态分配在任何情况下都是完全没有必要的。考虑:

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.

创建一个宏一样为元素数量创建一个符号

另请注意,如果您打算将这些结构放在 ROM 中——您通常拥有比 RAM 更多的 ROM,那么您应该声明它们 static const:

static const COORDINATES title_coord[]  = { /*constant initialiser coords*/ } ;
...
static const GRAPHIC_COORD graphicItemCoordinates[] = ...

感谢大家的参与!

我已经确定了一个在屏幕代码大小和效率方面都可行的解决方案。该解决方案确保存储的数据量等于我们实际需要的数据量。除了易于理解之外,我还认为该解决方案可被人类阅读。这很重要,因为新的团队成员可能会来来去去;花在学习曲线上的时间越少越好。最后,我相信在不可避免的需要创建新图形项目的情况下,这段代码将很容易扩展。下面是那个代码。即将推出的脚本将用于维护这个长期的完整版本。干杯。

/* 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;
}