如何在 C header 文件中构建复杂数组?
How to build a complex array in a C header file?
我正在尝试使用初始值设定项在 C header 文件中构建一个数组。这也可以使用可执行代码在函数中完成,但我认为 header 将是一个更优雅的解决方案。我对数据结构不是很了解,并且在搜索网络时很难找到解决方案。我的骨骼看起来像这样:
typedef struct fontTypeDef {
uint16_t xOffset;
uint16_t yOffset;
uint16_t width;
uint16_t height;
uint32_t* bitmapPtr;
} fontTypeDef;
fontTypeDef font[128];
font[32] = {0, 0, 8, 1 {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}};
font[33] = {1, 2, 3, 1 {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321}};
.
.
.
这是如何正确完成并提高内存效率的?
编辑 1: 很多好的反馈,暗示这不能仅在 header 文件中完成。我还应该提供更多背景知识。我已经编写了一个 Python 脚本,可以将给定的 true-type 字体转换为 header 文件(当然我也可以写入 .c 文件或其任意组合)。目的是在 STM32 µC 中使用位块传输,并在将文本写入 LCD 时利用 DMA 2D。我看到示例代码是一张图片,通过使用 header 文件中的定义简单地位块化,如下所示:
const uint32_t RGB565_480x272[65280] =
{
0x7A537A53,
0x82538253,
0x82538253,
0x8A538253,
0x82538253,
0x82538253,
.
.
}
编辑 2:这是@Eric Postpischil 概述的解决方案。
在 header 文件中:
typedef struct fontTypeDef {
uint16_t xOffset;
uint16_t yOffset;
uint16_t width;
uint16_t height;
const uint32_t *bitmapPtr;
} fontTypeDef;
extern const fontTypeDef font[128];
在 C 文件中:
const fontTypeDef font[128] =
{
…
[32] = {0, 0, 8, 1, (uint32_t []) {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}},
[33] = {1, 2, 3, 1, (uint32_t []) {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321}},
…
}
如果你创建了一个变量并且你想在你的源代码中使用它你必须让编译器知道你正在使用一个在头文件中通过关键字 extern
声明的变量所以你的典型头声明源使用的变量看起来像这样
//header.h
int a = 5;
//source.c
extern int a;
这告诉编译器不要为 a
生成变量,而是在头文件中查找它。
切勿在头文件中定义数据和函数(static inline
除外)。在头文件中仅放置类型声明、函数原型和 extern
对象声明。
头文件应该只包含font
数组的类型声明和外部声明(尽管使它成为全局当然不是“优雅”):
#if !defined FONT_H_INCL
#define FONT_H_INCL
typedef struct fontTypeDef {
uint16_t xOffset;
uint16_t yOffset;
uint16_t width;
uint16_t height;
uint32_t bitmap[8];
} fontTypeDef;
extern fontTypeDef font[128];
#endif
然后说 font.c 你会:
fontTypeDef font[128] = { {0, 0, 8, 1, {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}, // 0
{1, 2, 3, 1, {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321} // 1
...
{0, 0, 8, 1, {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0} // 126
{1, 2, 3, 1, {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321} }; // 127
你然后 include font.h,你引用 font
或 fontTypeDef
分别编译和 linkfont.c.
综上所述,如果“优雅”是您的目标,那么您真的不应该这样做。以这种方式使用全球数据是一种糟糕的做法,也是不必要的。而是在 .c 中使 font
静态并提供访问函数。
... but I think a header would be a more elegant solution
我不知道是什么让你这么想,但这是绝对错误的。
切勿将 fontTypeDef font[128];
之类的变量定义放入 header 文件中 - 切勿。
原因很简单。在具有多个源文件(即 c-files)的项目中,您需要能够将 header 文件包含在所有这些源文件中。如果 header 文件包含变量定义,那将是不可能的,因为在这种情况下,每个源文件都定义了该变量,您最终会得到多个同名变量。这是不允许的。
正确的方法是在源文件中定义变量并在 header 文件中放置一个外部声明。喜欢
Header
extern fontTypeDef font[128];
来源
fontTypeDef font[128];
现在您可以在任意数量的源文件中包含 header。
但是...即使这样也应该避免。一般来说,拥有全局范围的变量是个坏主意。尽可能缩小变量的范围。充其量将它们保留在函数中。如果那不可行,至少将范围限制为当前源文件:
static fontTypeDef font[128];
作为最后一条评论:
类似
font[32] = {0, 0, 8, 1 {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}};
不能放在函数外。
如果你想在函数外给 font
赋值,你必须使用像这样的初始化器:
static fontTypeDef font[128] = { ... };
此外,最后一个结构成员是一个指针,因此您不能使用像这样的初始化程序:
{0, 0, 8, 1, {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This can't initialize a pointer
不过你可以这样做
static fontTypeDef font[128] = {
{0, 0, 8, 1, NULL},
{0, 0, 4, 6, NULL}
};
稍后在某个函数中设置指针值。
把这个放在头文件中就可以达到你想要的效果:
typedef struct fontTypeDef {
uint16_t xOffset;
uint16_t yOffset;
uint16_t width;
uint16_t height;
const uint32_t *bitmapPtr;
} fontTypeDef;
extern const fontTypeDef font[128];
这在一个源文件中:
const fontTypeDef font[128] =
{
…
[32] = {0, 0, 8, 1, (const uint32_t []) {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}},
[33] = {1, 2, 3, 1, (const uint32_t []) {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321}},
…
}
备注:
- 这会将声明放在头文件中,使它们对包含头文件的所有源文件可见。它将数据的定义放在一个源文件中,这样数组
font
在整个程序中只会被定义一次。
- 尽管其他评论和答案警告不要使用“全局”变量或文件范围标识符,但以这种方式定义常量数据是合理的。只要您希望数据永久内置到程序中,而不是从参考文件中读取,就可以了。
const
已添加,因为我们定义的数据在构建程序时(或之前)是固定的,不应由程序更改。
- 指针成员使用复合文字初始化。语法
(<i>type</i>) { <i>initial values</i>… }
创建一个初始化对象。上面的代码使用它来创建一个 uint32_t
的数组。该数组自动转换为指向其第一个元素的指针,该指针初始化 bitmapPtr
元素。
font
数组的元素使用指定的初始化语法 [<i>index</i>] = { <i>initial values</i>…}
,其中index给出了被初始化的数组元素的索引。如果按顺序初始化所有数组元素,也可以简单地使用 { <i> 初始值 </i> ... }
的列表,省略 [<i>索引</i>] =
.
- 原始代码中初始值后的分号已更改为逗号,因为这现在是数组定义中的初始值设定项列表,而不是语句序列。
我正在尝试使用初始值设定项在 C header 文件中构建一个数组。这也可以使用可执行代码在函数中完成,但我认为 header 将是一个更优雅的解决方案。我对数据结构不是很了解,并且在搜索网络时很难找到解决方案。我的骨骼看起来像这样:
typedef struct fontTypeDef {
uint16_t xOffset;
uint16_t yOffset;
uint16_t width;
uint16_t height;
uint32_t* bitmapPtr;
} fontTypeDef;
fontTypeDef font[128];
font[32] = {0, 0, 8, 1 {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}};
font[33] = {1, 2, 3, 1 {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321}};
.
.
.
这是如何正确完成并提高内存效率的?
编辑 1: 很多好的反馈,暗示这不能仅在 header 文件中完成。我还应该提供更多背景知识。我已经编写了一个 Python 脚本,可以将给定的 true-type 字体转换为 header 文件(当然我也可以写入 .c 文件或其任意组合)。目的是在 STM32 µC 中使用位块传输,并在将文本写入 LCD 时利用 DMA 2D。我看到示例代码是一张图片,通过使用 header 文件中的定义简单地位块化,如下所示:
const uint32_t RGB565_480x272[65280] =
{
0x7A537A53,
0x82538253,
0x82538253,
0x8A538253,
0x82538253,
0x82538253,
.
.
}
编辑 2:这是@Eric Postpischil 概述的解决方案。
在 header 文件中:
typedef struct fontTypeDef {
uint16_t xOffset;
uint16_t yOffset;
uint16_t width;
uint16_t height;
const uint32_t *bitmapPtr;
} fontTypeDef;
extern const fontTypeDef font[128];
在 C 文件中:
const fontTypeDef font[128] =
{
…
[32] = {0, 0, 8, 1, (uint32_t []) {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}},
[33] = {1, 2, 3, 1, (uint32_t []) {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321}},
…
}
如果你创建了一个变量并且你想在你的源代码中使用它你必须让编译器知道你正在使用一个在头文件中通过关键字 extern
声明的变量所以你的典型头声明源使用的变量看起来像这样
//header.h
int a = 5;
//source.c
extern int a;
这告诉编译器不要为 a
生成变量,而是在头文件中查找它。
切勿在头文件中定义数据和函数(static inline
除外)。在头文件中仅放置类型声明、函数原型和 extern
对象声明。
头文件应该只包含font
数组的类型声明和外部声明(尽管使它成为全局当然不是“优雅”):
#if !defined FONT_H_INCL
#define FONT_H_INCL
typedef struct fontTypeDef {
uint16_t xOffset;
uint16_t yOffset;
uint16_t width;
uint16_t height;
uint32_t bitmap[8];
} fontTypeDef;
extern fontTypeDef font[128];
#endif
然后说 font.c 你会:
fontTypeDef font[128] = { {0, 0, 8, 1, {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}, // 0
{1, 2, 3, 1, {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321} // 1
...
{0, 0, 8, 1, {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0} // 126
{1, 2, 3, 1, {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321} }; // 127
你然后 include font.h,你引用 font
或 fontTypeDef
分别编译和 linkfont.c.
综上所述,如果“优雅”是您的目标,那么您真的不应该这样做。以这种方式使用全球数据是一种糟糕的做法,也是不必要的。而是在 .c 中使 font
静态并提供访问函数。
... but I think a header would be a more elegant solution
我不知道是什么让你这么想,但这是绝对错误的。
切勿将 fontTypeDef font[128];
之类的变量定义放入 header 文件中 - 切勿。
原因很简单。在具有多个源文件(即 c-files)的项目中,您需要能够将 header 文件包含在所有这些源文件中。如果 header 文件包含变量定义,那将是不可能的,因为在这种情况下,每个源文件都定义了该变量,您最终会得到多个同名变量。这是不允许的。
正确的方法是在源文件中定义变量并在 header 文件中放置一个外部声明。喜欢
Header
extern fontTypeDef font[128];
来源
fontTypeDef font[128];
现在您可以在任意数量的源文件中包含 header。
但是...即使这样也应该避免。一般来说,拥有全局范围的变量是个坏主意。尽可能缩小变量的范围。充其量将它们保留在函数中。如果那不可行,至少将范围限制为当前源文件:
static fontTypeDef font[128];
作为最后一条评论:
类似
font[32] = {0, 0, 8, 1 {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}};
不能放在函数外。
如果你想在函数外给 font
赋值,你必须使用像这样的初始化器:
static fontTypeDef font[128] = { ... };
此外,最后一个结构成员是一个指针,因此您不能使用像这样的初始化程序:
{0, 0, 8, 1, {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This can't initialize a pointer
不过你可以这样做
static fontTypeDef font[128] = {
{0, 0, 8, 1, NULL},
{0, 0, 4, 6, NULL}
};
稍后在某个函数中设置指针值。
把这个放在头文件中就可以达到你想要的效果:
typedef struct fontTypeDef {
uint16_t xOffset;
uint16_t yOffset;
uint16_t width;
uint16_t height;
const uint32_t *bitmapPtr;
} fontTypeDef;
extern const fontTypeDef font[128];
这在一个源文件中:
const fontTypeDef font[128] =
{
…
[32] = {0, 0, 8, 1, (const uint32_t []) {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0x0}},
[33] = {1, 2, 3, 1, (const uint32_t []) {0xABCDEF, 0x0, 0xFF, 0xAB, 0xCC, 0x12321}},
…
}
备注:
- 这会将声明放在头文件中,使它们对包含头文件的所有源文件可见。它将数据的定义放在一个源文件中,这样数组
font
在整个程序中只会被定义一次。 - 尽管其他评论和答案警告不要使用“全局”变量或文件范围标识符,但以这种方式定义常量数据是合理的。只要您希望数据永久内置到程序中,而不是从参考文件中读取,就可以了。
const
已添加,因为我们定义的数据在构建程序时(或之前)是固定的,不应由程序更改。- 指针成员使用复合文字初始化。语法
(<i>type</i>) { <i>initial values</i>… }
创建一个初始化对象。上面的代码使用它来创建一个uint32_t
的数组。该数组自动转换为指向其第一个元素的指针,该指针初始化bitmapPtr
元素。 font
数组的元素使用指定的初始化语法[<i>index</i>] = { <i>initial values</i>…}
,其中index给出了被初始化的数组元素的索引。如果按顺序初始化所有数组元素,也可以简单地使用{ <i> 初始值 </i> ... }
的列表,省略[<i>索引</i>] =
.- 原始代码中初始值后的分号已更改为逗号,因为这现在是数组定义中的初始值设定项列表,而不是语句序列。