嵌入式C语言中的结构是什么?

嵌入式技术

1367人已加入

描述

在介绍结构之后,将看一下这个强大数据对象的一些重要应用。然后将检查C语言语法以声明结构。最后将简要介绍数据对齐要求。可以通过简单地重新排列其成员的顺序来减小结构的大小。

结构

可以将在逻辑上彼此相关的同一类型的多个变量分组为数组。使用一个组而不是一组自变量,可以更方便地安排和使用数据。例如,可以定义以下数组来存储数字化语音输入的ADC的最后50个样本:

uint16_t voice[50];

请注意,uint16_t是无符号整数类型,宽度恰好为16位。这是在C标准库stdint.h中定义的,它提供了与系统规范无关的特定位长度的数据类型。数组可用于对多个具有相同数据类型的变量进行分组。如果不同数据类型的变量之间存在连接怎么办?可以在程序中将这些变量视为一个组吗?例如,假设需要指定生成上述语音 阵列的ADC的采样率。可以定义一个浮点变量来存储采样率:

float sample_rate;

尽管变量voice和sample_rate彼此相关,但它们被定义为两个独立变量。为了将这两个变量相互关联,我们可以使用称为结构的C语言的强大数据结构。结构允许将不同的数据类型进行分组并将它们作为单个数据对象处理。结构可以包括不同种类的变量类型,例如其他结构、指向函数的指针、指向结构的指针等。对于语音示例可以使用以下结构:

struct record {

uint16_t voice[50];

float sample_rate;

};

在这种情况下,有一个名为record的结构,它有两个不同的成员或字段:第一个成员是uint16_t元素的数组,第二个成员是float类型的变量。语法以关键字struct开头。struct关键字后面的单词是一个可选名称,用于以后引用该结构。将在本文的其余部分讨论定义和使用结构的其他细节。

结构的重要性

上面的例子指出了结构的一个重要应用,即定义了可以将不同类型的单个变量相互关联的依赖于应用程序的数据对象。这不仅可以实现操作数据的有效方式,还可以实现称为数据结构的专用结构。数据结构可用于各种应用,例如两个嵌入式系统之间的消息传递以及将从传感器收集的数据存储在非连续存储器位置中。

嵌入式系统图1.结构可用于实现链表

另外,当程序需要访问内存映射微控制器外围设备的寄存器时,结构是有用的数据对象。

嵌入式系统图2.STM32 MCU的存储器映射。图片由ARM

声明结构

要使用结构,首先需要指定结构模板。请考虑以下示例代码:

struct record {

uint16_t voice[4];

float sample_rate;

};

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

struct record mic1;

为变量mic1分配了一段内存。它有空间来存储数组的四个uint16_t元素和一个float变量。可以使用成员运算符(.)访问结构的成员。例如,以下代码将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;

该tag_name和variable_name是可选的标识符。我们通常会看到这两个标识符中的至少一个,但在某些情况下我们可以消除它们。语法1:当tag_name和variable_name都存在时,我们就在模板之后定义结构变量。使用此语法,我们可以重写上一个示例,如下所示:

struct record {

uint16_t voice[4];

float sample_rate;

} mic1;

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

struct record mic2;

语法2:仅包含variable_name。使用这种语法,我们可以重写上一节中的示例,如下所示:

struct {

uint16_t voice[4];

float sample_rate;

} mic1;

在这种情况下,我们必须在模板之后定义所有变量,并且我们不能在程序中定义任何其他变量(因为模板没有名称,我们以后也不能引用它)。

语法3:在这种情况下,没有tag_name或variable_name。以这种方式定义的结构模板称为匿名结构。可以在另一个结构或联合中定义匿名结构。下面给出一个例子:

struct test {

// Anonymous structure

struct {

float f;

char a;

};

} test_var;

要访问上述匿名结构的成员,我们可以使用成员运算符(.)。以下代码将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整除的内存位置。实现这种称为数据对齐要求的约束,以使处理器更有效地访问变量。数据对齐会导致内存布局中的一些浪费空间(或填充)。这个主题只在这里介绍; 我们将在本系列的下一篇文章中详细介绍。

嵌入式系统图3.数据对齐导致内存布局中的一些浪费

了解数据对齐要求后,我们可能能够重新排列结构中成员的顺序,并提高内存使用效率。例如,如果我们重写上面给出的结构,它的大小将在32位机器上减少到8个字节。

struct Test2{

uint32_t d;

uint16_t f;

uint8_t c;

uint8_t e;

} MyStruct;

对于内存受限的嵌入式系统,将数据对象的大小从12个字节减少到8个字节可以节省大量成本,特别是当程序需要许多这些数据对象时。

总结

结构允许我们定义依赖于应用程序的数据对象,这些对象可以将不同类型的单个变量相互关联,这就产生了一种有效的数据处理方法;称为数据结构的专用结构可用于各种应用,例如两个嵌入式系统之间的消息传递以及将从传感器收集的数据存储在非连续存储器位置中;当我们需要访问内存映射微控制器外围设备的寄存器时,结构是有用的;我们可以通过重新排列结构中成员的顺序来提高内存使用效率。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分