🌟 关注「嵌入式软件客栈」公众号 🌟,解锁实战技巧!💻🚀
在嵌入式开发中,随着项目的推进,重复造轮子、代码分散、维护困难等问题屡见不鲜。如何系统性地构建自己的通用函数库(“轮子库”),能够高效复用、持续进化,是每个开发者都会遇到的问题。
构建函数库好处
- 减少重复劳动:常用功能高度复用,避免“同一功能多处实现”。
- 提升开发效率:接口统一、文档完善,降低新成员上手门槛。
- 提升代码质量:集中优化、统一测试,减少隐蔽Bug。
- 便于维护和扩展:模块化设计,便于后续功能拓展和问题定位。
如何实现
需求梳理与模块划分
- 高频通用功能优先:如数据类型、内存操作、校验算法、文件/存储、数据结构、字符串处理、协议解析、日志、时间等。
- 模块边界清晰:每个模块只做一类事,接口单一、职责明确。
- 分层设计:基础工具库(如类型、内存、字符串)与业务无关,业务相关的通用模块(如协议、设备抽象)单独分层。
- 可移植性优先:尽量减少平台相关性,必要时用适配层隔离。
设计原则
- 接口简洁统一:命名规范、参数风格一致,避免“万能接口”。
- 文档与示例齐全:每个模块配套README和测试用例。
- 易于集成与裁剪:支持静态/动态库编译,Makefile组织清晰,便于裁剪。
通用模块设计与用法
数据类型与常用宏(types.h)
统一数据类型和常用宏,提升代码可读性和移植性。
// types.h
#ifndef TYPES_H
#define TYPES_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
typedef int8_t s8;
typedef uint8_t u8;
typedef int16_t s16;
typedef uint16_t u16;
typedef int32_t s32;
typedef uint32_t u32;
typedef float f32;
typedef double f64;
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof((arr)[0]))
#endif
校验算法模块(crc.h/.c)
提供常用CRC16/CRC32算法,接口简洁,便于移植。
// crc.h
#ifndef CRC_H
#define CRC_H
#include <stdint.h>
uint16_t crc16_ccitt(const uint8_t *data, uint32_t len, uint16_t init);
uint32_t crc32_simple(const uint8_t *data, uint32_t len, uint32_t init);
#endif
// crc.c
#include "crc.h"
uint16_t crc16_ccitt(const uint8_t *data, uint32_t len, uint16_t init) {
uint16_t crc = init;
for (uint32_t i = 0; i < len; ++i) {
crc ^= (uint16_t)data[i] << 8;
for (int j = 0; j < 8; ++j)
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : (crc << 1);
}
return crc;
}
uint32_t crc32_simple(const uint8_t *data, uint32_t len, uint32_t init) {
uint32_t crc = init;
for (uint32_t i = 0; i < len; ++i) {
crc ^= data[i];
for (int j = 0; j < 8; ++j)
crc = (crc & 1) ? (crc >> 1) ^ 0xEDB88320 : (crc >> 1);
}
return crc;
}
文件操作模块(fileutil.h/.c)
封装常用文件操作,接口安全、易用。
// fileutil.h
#ifndef FILEUTIL_H
#define FILEUTIL_H
#include <stddef.h>
int file_read_all(const char *path, void *buf, size_t maxlen);
int file_write_all(const char *path, const void *buf, size_t len);
#endif
// fileutil.c
#include "fileutil.h"
#include <stdio.h>
int file_read_all(const char *path, void *buf, size_t maxlen) {
FILE *fp = fopen(path, "rb");
if (!fp) return -1;
size_t n = fread(buf, 1, maxlen, fp);
fclose(fp);
return (int)n;
}
int file_write_all(const char *path, const void *buf, size_t len) {
FILE *fp = fopen(path, "wb");
if (!fp) return -1;
size_t n = fwrite(buf, 1, len, fp);
fclose(fp);
return (int)n;
}
通用数据结构模块
动态数组(dynarray.h/.c)
支持任意类型的动态数组,接口风格类似C++ vector。
// dynarray.h
#ifndef DYNARRAY_H
#define DYNARRAY_H
#include <stddef.h>
typedef struct {
void *data;
size_t elem_size;
size_t size;
size_t capacity;
} dynarray_t;
void dynarray_init(dynarray_t *arr, size_t elem_size);
void dynarray_free(dynarray_t *arr);
int dynarray_push_back(dynarray_t *arr, const void *elem);
void *dynarray_at(dynarray_t *arr, size_t idx);
#endif
// dynarray.c
#include "dynarray.h"
#include <stdlib.h>
#include <string.h>
void dynarray_init(dynarray_t *arr, size_t elem_size) {
arr->data = NULL; arr->elem_size = elem_size; arr->size = 0; arr->capacity = 0;
}
void dynarray_free(dynarray_t *arr) { free(arr->data); arr->data = NULL; arr->size = arr->capacity = 0; }
int dynarray_push_back(dynarray_t *arr, const void *elem) {
if (arr->size == arr->capacity) {
size_t newcap = arr->capacity ? arr->capacity * 2 : 4;
void *newdata = realloc(arr->data, newcap * arr->elem_size);
if (!newdata) return -1;
arr->data = newdata; arr->capacity = newcap;
}
memcpy((char*)arr->data + arr->size * arr->elem_size, elem, arr->elem_size);
arr->size++;
return 0;
}
void *dynarray_at(dynarray_t *arr, size_t idx) {
return (idx < arr->size) ? (char*)arr->data + idx * arr->elem_size : NULL;
}
简单哈希表(hashtable.h/.c)
适合配置、参数等场景,支持字符串key。
// hashtable.h
#ifndef HASHTABLE_H
#define HASHTABLE_H
#include <stddef.h>
typedef struct hashtable hashtable_t;
hashtable_t *hashtable_create(size_t buckets);
void hashtable_destroy(hashtable_t *ht);
int hashtable_set(hashtable_t *ht, const char *key, void *value);
void *hashtable_get(hashtable_t *ht, const char *key);
#endif
单向链表(slist.h/.c)
适合轻量场景,接口极简。
// slist.h
#ifndef SLIST_H
#define SLIST_H
typedef struct slist_node {
void *data;
struct slist_node *next;
} slist_node_t;
void slist_push_front(slist_node_t **head, void *data);
void slist_free(slist_node_t *head);
#endif
// slist.c
#include "slist.h"
#include <stdlib.h>
void slist_push_front(slist_node_t **head, void *data) {
slist_node_t *node = malloc(sizeof(slist_node_t));
node->data = data; node->next = *head; *head = node;
}
void slist_free(slist_node_t *head) {
while (head) { slist_node_t *tmp = head; head = head->next; free(tmp); }
}
JSON与结构体互转(jsonutil.h/.c)
以cJSON为例,封装结构体与JSON互转,便于配置、协议等场景。
// jsonutil.h
#ifndef JSONUTIL_H
#define JSONUTIL_H
#include <cJSON.h>
typedef struct {
int id;
char name[32];
} user_t;
cJSON *user_to_json(const user_t *user);
int json_to_user(const cJSON *json, user_t *user);
#endif
// jsonutil.c
#include "jsonutil.h"
cJSON *user_to_json(const user_t *user) {
cJSON *obj = cJSON_CreateObject();
cJSON_AddNumberToObject(obj, "id", user->id);
cJSON_AddStringToObject(obj, "name", user->name);
return obj;
}
int json_to_user(const cJSON *json, user_t *user) {
if (!cJSON_IsObject(json)) return -1;
user->id = cJSON_GetObjectItem(json, "id")->valueint;
strncpy(user->name, cJSON_GetObjectItem(json, "name")->valuestring, sizeof(user->name));
return 0;
}
规范保持
- 统一目录结构与命名规范:如
libcommon/下分模块存放,头文件集中到include/,命名风格全局统一。 - 接口文档与用法示例:每个模块头文件注释、README、典型用法代码。
- 定期重构:定期梳理库中冗余、过时代码。
关注 嵌入式软件客栈 公众号,获取更多内容

234

被折叠的 条评论
为什么被折叠?



