结构体与共用体的异同及应用(嵌入式)

在 C 语言中,共用体(Union)和结构体(Structure)是两种用于组合不同数据类型的自定义数据类型,它们在内存使用、数据存储和访问方式等方面存在明显差异,下面为你详细介绍:

结构体

定义和语法

结构体是一种将不同类型的数据组合在一起的自定义数据类型,它允许用户将多个相关的变量封装在一个单独的实体中。结构体的定义使用 struct 关键字,语法如下:

struct 结构体名 {
    数据类型 成员1;
    数据类型 成员2;
    // 可以有更多成员
};
示例代码
#include <stdio.h>

// 定义一个结构体表示学生信息
struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    // 声明一个结构体变量
    struct Student stu;

    // 给结构体成员赋值
    strcpy(stu.name, "John");
    stu.age = 20;
    stu.score = 85.5;

    // 输出结构体成员的值
    printf("Name: %s\n", stu.name);
    printf("Age: %d\n", stu.age);
    printf("Score: %.2f\n", stu.score);

    return 0;
}
特点

  • 内存分配:结构体的每个成员都有自己独立的内存空间,结构体的总大小是其所有成员大小之和,可能还会有一些内存对齐带来的额外开销。
  • 成员访问:可以通过点运算符(.)来访问结构体的各个成员,例如 stu.namestu.age 等。
  • 数据存储:结构体中的成员可以同时存储不同的值,各个成员之间相互独立,修改一个成员的值不会影响其他成员。

共用体

定义和语法

共用体也是一种自定义数据类型,它允许不同的数据类型共享同一块内存空间。共用体的定义使用 union 关键字,语法如下:

union 共用体名 {
    数据类型 成员1;
    数据类型 成员2;
    // 可以有更多成员
};
示例代码
#include <stdio.h>

// 定义一个共用体
union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    // 声明一个共用体变量
    union Data data;

    // 给共用体成员赋值
    data.i = 10;
    printf("data.i: %d\n", data.i);

    data.f = 220.5;
    printf("data.f: %.2f\n", data.f);

    strcpy(data.str, "C Programming");
    printf("data.str: %s\n", data.str);

    return 0;
}
特点

  • 内存分配:共用体的所有成员共享同一块内存空间,共用体的大小是其最大成员的大小。
  • 成员访问:同样通过点运算符(.)来访问共用体的各个成员,例如 data.idata.f 等。
  • 数据存储:在同一时间,共用体只能存储一个成员的值。当给一个成员赋值时,会覆盖之前存储在该内存空间中的其他成员的值。

共用体和结构体的区别

  • 内存使用:结构体的成员各自占用独立的内存空间,内存大小是所有成员大小之和;共用体的成员共享同一块内存空间,内存大小取决于最大成员的大小。
  • 数据存储:结构体可以同时存储多个成员的值,各个成员相互独立;共用体同一时间只能存储一个成员的值,赋值操作会覆盖其他成员的值。
  • 应用场景:结构体适用于需要将多个相关的数据组合在一起的场景,如表示一个人的信息、一个设备的配置参数等;共用体适用于需要在不同数据类型之间共享内存,以节省内存空间的场景,如在解析不同类型的数据时,或者在某些硬件寄存器操作中。

在嵌入式系统开发中,结构体和共用体是非常实用的数据类型,它们各自具有独特的特性,在不同的场景下发挥着重要作用,以下为你详细介绍它们的应用:

结构体的应用

1. 数据封装与管理

  • 设备配置信息:嵌入式系统中会涉及各种设备,如传感器、通信模块等。可以使用结构体将设备的配置参数封装在一起,方便管理和传递。
// 定义一个结构体表示传感器的配置信息
struct SensorConfig {
    uint8_t samplingRate;  // 采样率
    uint16_t resolution;   // 分辨率
    uint8_t gain;          // 增益
};

// 使用示例
void configureSensor(struct SensorConfig *config) {
    // 根据配置信息对传感器进行配置
    // ...
}

int main() {
    struct SensorConfig sensorConfig = {10, 12, 2};
    configureSensor(&sensorConfig);
    return 0;
}

  • 任务控制块:在实时操作系统(RTOS)中,每个任务都有自己的控制块,用于存储任务的状态、优先级、堆栈指针等信息。结构体可以很好地实现任务控制块的封装。
// 定义一个结构体表示任务控制块
struct TaskControlBlock {
    uint8_t taskID;           // 任务 ID
    uint8_t priority;         // 任务优先级
    uint32_t *stackPointer;   // 堆栈指针
    uint8_t taskState;        // 任务状态
};
2. 数据传输与通信

  • 协议数据单元(PDU):在通信协议中,数据通常以特定的格式进行传输,结构体可以用来定义这些协议数据单元。
// 定义一个结构体表示简单的通信协议帧
struct CommunicationFrame {
    uint8_t header;           // 帧头
    uint16_t dataLength;      // 数据长度
    uint8_t data[100];        // 数据内容
    uint8_t checksum;         // 校验和
};

// 发送帧的函数
void sendFrame(struct CommunicationFrame *frame) {
    // 将帧数据通过通信接口发送出去
    // ...
}
3. 硬件寄存器映射

  • 访问外设寄存器:嵌入式系统需要与各种外设进行交互,通过结构体可以将外设的寄存器映射到内存中,方便对寄存器进行读写操作。
// 定义一个结构体表示 GPIO 寄存器
struct GPIO_Registers {
    volatile uint32_t MODER;   // 模式寄存器
    volatile uint32_t OTYPER;  // 输出类型寄存器
    volatile uint32_t OSPEEDR; // 输出速度寄存器
    volatile uint32_t PUPDR;   // 上拉/下拉寄存器
    // 其他寄存器...
};

// 假设 GPIOA 的基地址为 0x40020000
#define GPIOA_BASE_ADDRESS 0x40020000
struct GPIO_Registers *GPIOA = (struct GPIO_Registers *)GPIOA_BASE_ADDRESS;

// 使用示例:设置 GPIOA 的模式
GPIOA->MODER = 0x00000001;

共用体的应用

1. 节省内存空间

  • 不同类型数据的复用:在嵌入式系统中,内存资源通常比较有限。当需要存储不同类型的数据,但同一时间只使用其中一种类型时,可以使用共用体来节省内存。
// 定义一个共用体表示不同类型的传感器数据
union SensorData {
    int temperature;     // 温度数据
    float pressure;      // 压力数据
    uint16_t humidity;   // 湿度数据
};

// 使用示例
void processSensorData(union SensorData data, uint8_t sensorType) {
    switch (sensorType) {
        case 0:
            // 处理温度数据
            printf("Temperature: %d\n", data.temperature);
            break;
        case 1:
            // 处理压力数据
            printf("Pressure: %.2f\n", data.pressure);
            break;
        case 2:
            // 处理湿度数据
            printf("Humidity: %u\n", data.humidity);
            break;
    }
}
2. 数据解析与转换

  • 位操作与数据拆分:共用体可以用于将一个数据按照不同的方式进行解析,例如将一个整数拆分为多个字节,或者将多个字节组合成一个整数。
// 定义一个共用体用于数据解析
union DataParser {
    uint32_t value;
    uint8_t bytes[4];
};//正是因为公用一块内存,下面才能通过bytes[]分别访问每个字节

// 使用示例:将一个 32 位整数拆分为 4 个字节
union DataParser parser;
parser.value = 0x12345678;
printf("Byte 0: 0x%02X\n", parser.bytes[0]);
printf("Byte 1: 0x%02X\n", parser.bytes[1]);
printf("Byte 2: 0x%02X\n", parser.bytes[2]);
printf("Byte 3: 0x%02X\n", parser.bytes[3]);
3. 硬件寄存器操作

  • 不同访问方式的统一:有些硬件寄存器可以通过不同的方式进行访问,例如按位访问或按字节访问。共用体可以将这些不同的访问方式统一起来。
// 定义一个共用体表示一个 16 位的寄存器
union Register16 {
    uint16_t value;
    struct {
        uint8_t lowByte;
        uint8_t highByte;
    } bytes;
    struct {
        uint16_t bit0: 1;
        uint16_t bit1: 1;
        // 其他位...
    } bits;
};

// 使用示例:按字节访问寄存器
union Register16 reg;
reg.bytes.lowByte = 0x12;
reg.bytes.highByte = 0x34;
printf("Register value: 0x%04X\n", reg.value);

        在给定的共用体 Register16 中,第二个结构体 bits 的作用是提供对 16 位寄存器中每一位的单独访问。通过位域(bit-field)的方式,我们可以将一个 16 位的无符号整数 value 拆分成 16 个独立的位,每个位都可以单独进行读写操作。这种方式在需要对寄存器的每一位进行精确控制的场景中非常有用,比如在嵌入式系统中对硬件寄存器的位操作。

下面是一个详细的示例,展示了如何使用 bits 结构体来访问和操作 16 位寄存器的每一位:

#include <stdio.h>
#include <stdint.h>

// 定义一个共用体表示一个 16 位的寄存器
union Register16 {
    uint16_t value;
    struct {
        uint8_t lowByte;
        uint8_t highByte;
    } bytes;
    struct {
        uint16_t bit0: 1;
        uint16_t bit1: 1;
        uint16_t bit2: 1;
        uint16_t bit3: 1;
        uint16_t bit4: 1;
        uint16_t bit5: 1;
        uint16_t bit6: 1;
        uint16_t bit7: 1;
        uint16_t bit8: 1;
        uint16_t bit9: 1;
        uint16_t bit10: 1;
        uint16_t bit11: 1;
        uint16_t bit12: 1;
        uint16_t bit13: 1;
        uint16_t bit14: 1;
        uint16_t bit15: 1;
    } bits;
};

int main() {
    // 创建一个 Register16 类型的共用体变量
    union Register16 reg;

    // 初始化寄存器的值
    reg.value = 0xABCD;  // 二进制表示为 1010 1011 1100 1101

    // 访问寄存器的某一位
    printf("Bit 0: %d\n", reg.bits.bit0);  // 输出第 0 位的值
    printf("Bit 15: %d\n", reg.bits.bit15); // 输出第 15 位的值

    // 修改寄存器的某一位
    reg.bits.bit3 = 1;  // 将第 3 位设置为 1
    reg.bits.bit12 = 0; // 将第 12 位设置为 0

    // 输出修改后寄存器的值
    printf("Modified register value: 0x%04X\n", reg.value);

    return 0;
}

代码解释

  1. 共用体定义:定义了一个名为 Register16 的共用体,其中包含三个成员:value 用于存储 16 位的整数值,bytes 结构体用于将 16 位值拆分为高字节和低字节,bits 结构体用于将 16 位值拆分为 16 个独立的位。
  2. 创建共用体变量:在 main 函数中,创建了一个 Register16 类型的共用体变量 reg
  3. 初始化寄存器值:将 reg.value 初始化为 0xABCD,即二进制的 1010 1011 1100 1101
  4. 访问寄存器的某一位:使用 reg.bits.bitX 的方式访问寄存器的第 X 位,并将其值输出。
  5. 修改寄存器的某一位:通过赋值操作修改寄存器的某一位,例如 reg.bits.bit3 = 1 将第 3 位设置为 1。
  6. 输出修改后寄存器的值:使用 printf 函数输出修改后寄存器的值。

通过这种方式,我们可以方便地对 16 位寄存器的每一位进行单独的读写操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值