一、共用体(Union)

共用体是一种特殊的自定义数据类型,其核心特性是所有成员共享同一块内存空间—— 这意味着,同一时间只有一个成员能被有效访问,修改一个成员会覆盖其他成员的值。

1. 定义与内存布局

// 定义一个包含int、char、double的共用体
union Data {
    int i;
    char c;
    double d;
};
  • 共用体的大小等于最大成员的字节数(上述例子中是double的 8 字节)。
  • 所有成员的内存起始地址相同,比如&data.i == &data.c == &data.d

2. 经典应用场景

(1)判断系统大小端

大小端是数据存储的字节顺序:小端(低字节存低地址)是主流架构(x86、ARM),大端(低字节存高地址)常见于网络传输、部分嵌入式设备。

利用共用体可以快速判断:

#include <stdio.h>

union EndianTest {
    int num;
    char byte;
};

int main() {
    union EndianTest test;
    test.num = 1; // 二进制:00000000 00000000 00000000 00000001
    
    // 小端:byte取低地址,值为1;大端:byte取高地址,值为0
    if (test.byte == 1) {
        printf("小端存储\n");
    } else {
        printf("大端存储\n");
    }
    return 0;
}
(2)节省内存的 “变体数据”

在嵌入式系统中,内存资源紧张时,可用共用体存储 “互斥使用” 的数据。比如串口通信中,数据可能是字符或整数:

union CommData {
    char cmd;    // 命令(字符型)
    int value;   // 参数(整型)
};

// 同一帧数据要么传命令,要么传参数,无需为两者分配独立空间
void handle_comm(union CommData data, int type) {
    if (type == 0) {
        printf("收到命令:%c\n", data.cmd);
    } else {
        printf("收到参数:%d\n", data.value);
    }
}

二、枚举(Enum)

枚举用于定义有限的、命名的常量集合,替代魔法数字(直接写的数字),让代码可读性和可维护性翻倍。

1. 基础用法与自定义值

// 默认从0开始递增,也可手动指定值
enum Week {
    Mon = 1, Tue, Wed, Thu, Fri, Sat = 6, Sun  // Tue=2, Wed=3... Sun=7
};

// 枚举变量只能取枚举值范围内的值
enum Week today = Wed;

2. 实战案例:状态机设计

在嵌入式或游戏开发中,状态机是常用模式,枚举能清晰定义状态:

#include <stdio.h>

enum State {
    IDLE,       // 空闲
    RUNNING,    // 运行
    PAUSED,     // 暂停
    STOPPED     // 停止
};

void handle_state(enum State state) {
    switch (state) {
        case IDLE: printf("系统空闲,等待指令\n"); break;
        case RUNNING: printf("系统运行中...\n"); break;
        case PAUSED: printf("系统已暂停\n"); break;
        case STOPPED: printf("系统已停止\n"); break;
        default: printf("无效状态\n");
    }
}

int main() {
    enum State sys_state = RUNNING;
    handle_state(sys_state); // 输出:系统运行中...
    return 0;
}

3. 枚举的 “坑”:本质是整型

枚举值本质是int类型,可被赋值为枚举外的整数,需注意校验:

enum Week day = 10; // 语法不报错,但逻辑错误

三、位操作

位操作直接对二进制位进行运算,是驱动开发、嵌入式编程的核心技能 —— 比如操作寄存器、处理标志位、数据压缩等。

1. 核心运算符与常用技巧

运算符 作用 示例(a=0b1010, b=0b0110)
& 按位与(清零、检测) a&b=0b0010
| 按位或(置 1) a|b=0b1110
^ 按位异或(翻转、交换) a^b=0b1100
~ 按位取反(取反码) ~a=0b0101(假设 4 位)
<< 左移(乘 2ⁿ) a<<1=0b0100
>> 右移(除 2ⁿ) a>>1=0b0101
(1)寄存器位操作

嵌入式中常需操作寄存器的某几位(比如 GPIO 引脚配置):

#define REG 0x40000000 // 寄存器地址

// 1. 将REG的bit3置1(其他位不变)
*(unsigned int*)REG |= (1 << 3);

// 2. 将REG的bit5清零
*(unsigned int*)REG &= ~(1 << 5);

// 3. 翻转REG的bit7
*(unsigned int*)REG ^= (1 << 7);

// 4. 检测REG的bit2是否为1
if (*(unsigned int*)REG & (1 << 2)) {
    printf("bit2为1\n");
}
(2)高效交换两个数

用异或实现无临时变量交换:

void swap(int *a, int *b) {
    if (a != b) { // 避免自身交换导致值为0
        *a ^= *b;
        *b ^= *a;
        *a ^= *b;
    }
}

int main() {
    int x = 3, y = 5;
    swap(&x, &y);
    printf("x=%d, y=%d\n", x, y); // 输出:x=5, y=3
    return 0;
}

四、堆内存管理:malloc/free 的深度实战

堆内存是程序运行时动态分配的内存(栈内存是静态分配),由malloc申请、free释放,是处理大数据(如数组、结构体)的关键。

1. malloc:动态申请内存

#include <stdlib.h> // 需包含头文件

// 语法:void* malloc(size_t size);
// size:申请的字节数,返回void*指针,需强制类型转换

// 示例1:申请10个int的数组(40字节)
int *arr = (int*)malloc(10 * sizeof(int));
if (arr == NULL) { // 必须检查!申请失败返回NULL(如内存不足)
    perror("malloc failed"); // 打印错误原因
    return -1;
}

// 示例2:申请结构体数组
typedef struct {
    int id;
    char name[20];
} Student;

Student *stu_arr = (Student*)malloc(5 * sizeof(Student));
if (stu_arr == NULL) {
    perror("malloc failed");
    return -1;
}

2. free:释放堆内存

堆内存不会自动释放,用完必须free,否则导致内存泄漏(可用内存越来越少):

free(arr);   // 释放数组内存
arr = NULL;  // 避免悬空指针(指向已释放内存的指针)

free(stu_arr);
stu_arr = NULL;

3. 常见坑点与避坑指南

(1)内存泄漏
void bad_func() {
    int *p = (int*)malloc(4);
    // 未free(p),函数结束后p销毁,内存无法访问→泄漏
}
(2)重复释放
int *p = (int*)malloc(4);
free(p);
free(p); // 重复free→程序崩溃
(3)悬空指针
int *p = (int*)malloc(4);
free(p);
*p = 10; // 操作已释放的内存→未定义行为(崩溃/数据错乱)
(4)申请大小计算错误
// 错误:sizeof(int*)是指针大小(8字节),不是int大小(4字节)
int *arr = (int*)malloc(10 * sizeof(int*)); 
// 正确:用sizeof(int)或sizeof(*arr)
int *arr = (int*)malloc(10 * sizeof(*arr));

4. 进阶:realloc 动态调整内存

realloc可修改已分配内存的大小(扩大 / 缩小):

// 将arr从10个int扩大到20个int
int *new_arr = (int*)realloc(arr, 20 * sizeof(int));
if (new_arr == NULL) {
    perror("realloc failed");
    free(arr); // 原内存未释放,需手动free
    return -1;
}
arr = new_arr; // 指向新内存

总结:进阶特性的核心价值

  • 共用体:用内存共享实现 “互斥数据” 的高效存储,适合资源受限场景。
  • 枚举:用语义化常量替代魔法数字,大幅提升代码可读性。
  • 位操作:直击硬件底层,实现高效的位级控制,是嵌入式 / 驱动开发的必备技能。
  • 堆内存:突破栈内存大小限制,灵活管理动态数据,但需严格遵循 “申请 - 释放” 规则,避免内存问题。
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐