嵌入式技术论坛
直播中

白纪龙

8年用户 1305经验值
擅长:连接器 电源/新能源 连接器 模拟技术 连接器 测量仪表 连接器 EMC/EMI设计 连接器 嵌入式技术 连接器 连接器 连接器 连接器 接口/总线/驱动 连接器 处理器/DSP 连接器 光电显示 连接器 控制/MCU 连接器 连接器 连接器 MEMS/传感技术 连接器 电源/新能源 MEMS/传感技术 测量仪表 嵌入式技术 模拟技术 连接器 EMC/EMI设计 光电显示 处理器/DSP 接口/总线/驱动 控制/MCU
私信 关注
[经验]

理解嵌入式 c: 什么是结构?


本文提供了一些关于嵌入式 c 语言编程结构的基本信息。
在介绍结构之后,我们将看一下这个强大的数据对象的一些重要应用程序。然后,我们将研究 c 语言语法来声明一个结构。最后,我们将简要介绍数据对齐需求。我们将看到,我们可以通过简单地重新排列其成员的顺序来减小结构的大小。
结构
可以将许多在逻辑上相互关联的同一类型的变量组合为一个数组。处理一组数据而不是一组独立的变量使我们能够更方便地安排和使用这些数据。例如,我们可以定义以下数组来存储将语音输入数字化的 ADC 的最后50个样本:
uint16_t voice[50];

请注意,uint16 _ t 是一个无符号整数类型,宽度正好为16位。这在 c 标准库 stdint.h 中定义,该库提供与系统规范无关的特定位长度的数据类型。
数组可以用于对具有相同数据类型的许多变量进行分组。如果不同数据类型的变量之间存在连接会怎样?在我们的程序中,我们能否把这些变量作为一个组来处理?例如,假设我们需要指定生成上述语音阵列的 ADC 的采样率。我们可以定义一个浮点变量来存储采样率:
float sample_rate;

虽然变量语音和采样率是相互关联的,但它们被定义为两个独立的变量。为了将这两个变量相互关联,我们可以使用 c 语言中一种称为结构的强大数据构造。结构允许我们对不同的数据类型进行分组,并将它们作为单个数据对象处理。结构可以包含不同类型的变量,如其他结构、指向函数的指针、指向结构的指针等。对于语音示例,我们可以使用以下结构:
struct record {
        uint16_t voice[50];
        float sample_rate;
};

在这种情况下,我们有一个名为 record 的结构,它有两个不同的成员或字段: 第一个成员是 uint16 _ t 元素的数组,第二个成员是 float 类型的变量。语法以关键字 struct 开头。Struct 关键字后面的单词是一个可选的名称,稍后用于引用该结构。在本文的其余部分中,我们将讨论定义和使用结构的其他细节。
为什么结构很重要?
上面的示例指出了结构的一个重要应用,即定义可以将不同类型的单个变量相互关联的应用程序相关数据对象。这不仅导致了一种操纵数据的有效方法,而且允许我们实现称为数据结构的特殊结构。
数据结构可用于各种应用程序,例如两个嵌入式系统之间的消息传递和将从传感器收集的数据存储在非连续的内存位置。
image (3).png


图1. 结构可以用来实现链表

此外,当程序需要访问存储器映射的微控制器外设的寄存器时,结构是有用的数据对象。我们将在下一篇文章中研究结构化应用程序。
image (4).png

图2. STM32单片机的内存图。图片由 ARM 嵌入式系统提供

声明一个结构
要使用结构,我们首先需要指定一个结构模板:
struct record {
        uint16_t voice[4];
        float sample_rate;
};

这将指定用于创建此类型未来变量的布局或模板。此模板包括 uint16 _ t 数组和 float 类型的变量。模板的名称是 record,位于关键字 struct 之后。值得一提的是,存储结构模板没有内存分配。只有在定义了基于此布局的结构变量之后,才会进行内存分配。下面的代码声明了上面模板的变量 mic1:
struct record mic1;

现在,为变量 mic1分配一段内存。它有空间存储数组的四个 uint16 _ t 元素和一个浮点变量。
可以使用成员操作符(.)访问结构的成员.例如,下面的代码将100赋给数组的第一个元素,并将 sample _ rate 的值复制到 fs 变量(必须是 float 类型)。
mic1.voice[0]=100;
fs=mic1.sample_rate;

声明结构的其他方法
我们在上一节中讨论了一种声明结构的方法。C 语言支持其他一些格式,本节将对这些格式进行介绍。在整个程序中,您可能会坚持使用一种格式,但是熟悉其他格式有时会很有帮助。
声明结构模板的一般语法是:
struct tag_name {
        type_1         member_1;
        type_2         member_2;
        …
        type_n         member_n;

} variable_name;

标记 _ name 和变量 _ name 是可选的标识符。我们通常会看到这两个标识符中的至少一个,但在某些情况下,我们可以同时消除它们。
语法1: 当标记 _ name 和变量 _ name 都存在时,我们将在模板之后定义结构变量。使用这个语法,我们可以重写前面的例子如下:
struct record {
        uint16_t         voice[4];
        float                 sample_rate;
} mic1;

现在,如果我们需要定义另一个变量(mic2) ,我们可以编写
struct record mic2;

语法2: 只包含变量 _ name。使用这个语法,我们可以重写上一节中的例子,如下所示:
struct {
        uint16_t         voice[4];
        float                 sample_rate;
} mic1;

在这种情况下,我们必须在模板之后定义所有的变量,并且我们不能在以后的程序中定义任何其他变量(因为模板没有名称,我们以后不能引用它)。
语法3: 在这种情况下,没有标记 _ name 或变量 _ name。以这种方式定义的结构模板称为匿名结构。可以在另一个结构或联合中定义匿名结构。以下是一个例子:
struct test {
// Anonymous structure
    struct {
          float f;
          char a;
    };
} test_var;

To access the members of the above anonymous structure, we can use the member operator (.). The following code assigns 1.2 to the member f.
要访问上述匿名结构的成员,我们可以使用成员运算符(.).下面的代码将1.2分配给成员 f。
test_var.f=1.2;

由于结构是匿名的,我们只使用成员操作符访问它的成员一次。如果它有下面例子中的名字,我们将不得不使用成员操作符两次:
struct test {
         struct {
              float f;
              char a;
         } nested;
} test_var;

在这种情况下,我们应该使用以下代码将1.2分配给 f:
test_var.nested.f=1.2;

正如您所看到的,匿名结构可以使代码更具可读性并减少冗长。还可以使用 typedef 关键字和结构来定义新的数据类型。我们将在以后的文章中讨论这种方法。
结构的内存布局
C 标准保证结构的成员将按照成员在结构中声明的顺序一个接一个地位于内存中。第一个成员的内存地址将与结构本身的地址相同。考虑下面的例子:
struct Test2{
        uint8_t            c;
        uint32_t    d;
        uint8_t            e;
        uint16_t    f;
} MyStruct;

将分配四个内存位置来存储变量 c、 d、 e 和 f。内存位置的顺序将与声明成员的顺序相匹配: c 的位置将具有最低的地址,然后出现 d、 e 和最后的 f。我们需要多少字节来存储这个结构?考虑到变量的大小,我们知道至少需要1 + 4 + 1 + 2 = 8字节来存储这个结构。然而,如果我们为一台32位机器编译这段代码,我们会惊奇地发现 MyStruct 的大小是12字节而不是8!这是由于编译器在为结构的不同成员分配内存时具有某些约束。例如,32位整数只能存储在地址可被4整除的内存位置。这些约束被称为数据对齐要求,实现这些约束是为了让处理器更有效地访问变量。数据对齐会导致内存布局中的一些空白(或填充)。这里只介绍这个主题; 我们将在本系列的下一篇文章中详细介绍。
e7b2c193-e6a4-4c53-9889-309070e04666.png

图3. 数据对齐导致内存布局中浪费了一些空间(或填充)

了解了数据对齐的要求,我们就可以重新排列结构中成员的顺序,从而提高内存使用的效率。例如,如果我们按照下面给出的方式重写上面的结构,那么在32位机器上它的大小将减少到8字节。
struct Test2{
        uint32_t    d;
        uint16_t    f;
        uint8_t            c;  
        uint8_t            e;
} MyStruct;

对于内存受限的嵌入式系统,将数据对象的大小从12个字节减少到8个字节可以节省大量资源,特别是当程序需要许多这样的数据对象时。
下一篇文章将更详细地讨论数据对齐,并研究在嵌入式系统中使用结构的一些示例。
摘要
  • 结构允许我们定义依赖于应用程序的数据对象,这些数据对象可以将不同类型的单个变量彼此关联。这导致了一种操纵数据的有效方法。
  • 专门的结构,称为数据结构,可以用于各种应用程序,例如两个嵌入式系统之间的消息传递,以及将从传感器收集的数据存储在不连续的内存位置。
  • 当我们需要访问存储器映射微控制器外设的寄存器时,结构是有用的。
  • 我们可以通过在结构中重新排列成员的顺序来提高内存使用效率。
  • image (1).png
  • image (2).png
  • image.png

更多回帖

发帖
×
20
完善资料,
赚取积分