STM32开发中那些可正常调用但极少使用的C标准冷门函数
-
- 1、 引言
- 2、为什么关注冷门函数?
-
- 2.1、 资源受限环境下的优化
- 2.2、 避免重复造轮子
- 2.3 、代码可移植性
- 3、 字符串处理冷门函数
-
- 3.1、 `strxfrm()` – 字符串转换函数
- 3.2、 `strcoll()` – 区域设置相关的字符串比较
- 4、 内存操作冷门函数
-
- 4.1、 `memccpy()` – 带终止字符的内存复制
- 4.2、 `wmemcpy()` / `wmemmove()` – 宽字符内存操作
- 5、 数学函数中的冷门函数
-
- 5.1、 `remquo()` – 同时计算余数和商
- 5.2 、`nextafter()` / `nexttoward()` – 获取相邻浮点数
- 6、输入输出冷门函数
-
- 6.1、 `ungetc()` – 将字符推回输入流
- 7、 实用但少用的工具函数
-
- 7.1、 `div()` / `ldiv()` / `lldiv()` – 同时计算商和余数
- 7.2、 `bsearch()` – 二分查找
- 7.3、`qsort` -快速排序
- 8、 在STM32项目中使用的建议
-
- 8.1 、需要考虑的因素
- 8.2、 推荐使用场景
- 9、 结论
- 二、代码示例
-
- 1、示例代码
- 2、运行结果

1、 引言
在STM32嵌入式开发中,我们经常使用C标准库中的常见函数,如printf、malloc、strcpy等。然而,C标准库中还存在一些"冷门"函数,它们在STM32上可以正常调用,但由于使用场景特殊或功能重叠,很少被开发者注意到。本文将介绍这些函数,探讨它们的适用场景,并分析在STM32项目中是否值得使用。
2、为什么关注冷门函数?
2.1、 资源受限环境下的优化
STM32作为嵌入式微控制器,通常运行在资源受限的环境中。了解这些冷门函数可能带来:
- 更小的代码体积
- 更高的执行效率
- 更少的内存占用
2.2、 避免重复造轮子
许多开发者会自己实现某些功能,而标准库中可能已有现成的、经过充分测试的实现。
2.3 、代码可移植性
使用标准函数而非自定义实现,可以提高代码在不同平台间的可移植性。
3、 字符串处理冷门函数
3.1、 strxfrm() – 字符串转换函数
#include <string.h>
size_t strxfrm(char *dest, const char *src, size_t n);
功能:根据当前区域设置转换字符串,用于本地化字符串比较。
STM32适用性:
- 在需要多语言支持的嵌入式界面中可能有用
- 但STM32项目通常使用固定编码,很少需要本地化
- 函数实现较复杂,可能增加代码体积
3.2、 strcoll() – 区域设置相关的字符串比较
#include <string.h>
int strcoll(const char *s1, const char *s2);
功能:根据当前区域设置比较两个字符串。
使用示例:
// 在STM32上可以正常调用,但很少使用
const char *str1 = "äpple";
const char *str2 = "apple";
int result = strcoll(str1, str2); // 考虑区域设置的比较
4、 内存操作冷门函数
4.1、 memccpy() – 带终止字符的内存复制
#include <string.h>
void *memccpy(void *dest, const void *src, int c, size_t n);
功能:从src复制最多n个字节到dest,如果遇到字符c则停止。
STM32适用性分析:
// 传统做法
char buffer[100];
const char *source = "Hello,World,Test";
char *comma = strchr(source, ',');
size_t len = comma ? (comma – source) : strlen(source);
memcpy(buffer, source, len);
buffer[len] = '\\0';
// 使用memccpy
memccpy(buffer, source, ',', sizeof(buffer));
// 自动在逗号处停止,但不会自动添加'\\0'
优缺点:
- 优点:在某些场景下代码更简洁
- 缺点:不会自动添加字符串终止符,容易造成bug
4.2、 wmemcpy() / wmemmove() – 宽字符内存操作
#include <wchar.h>
wchar_t *wmemcpy(wchar_t *dest, const wchar_t *src, size_t n);
wchar_t *wmemmove(wchar_t *dest, const wchar_t *src, size_t n);
STM32适用性:
- 在需要Unicode支持的显示系统中可能有用
- 但STM32项目通常使用UTF-8或ASCII,很少使用宽字符
- 增加代码体积和内存占用
5、 数学函数中的冷门函数
5.1、 remquo() – 同时计算余数和商
#include <math.h>
double remquo(double x, double y, int *quo);
float remquof(float x, float y, int *quo);
long double remquol(long double x, long double y, int *quo);
功能:计算浮点数除法的余数,同时通过指针返回商。
STM32使用场景:
// 在需要同时获取商和余数的DSP算法中
float dividend = 10.5f;
float divisor = 3.2f;
int quotient;
float remainder = remquof(dividend, divisor, "ient);
// 等价于:
// float remainder = fmodf(dividend, divisor);
// int quotient = (int)(dividend / divisor);
// 但remquof可能更精确,因为商和余数是一次计算出来的
5.2 、nextafter() / nexttoward() – 获取相邻浮点数
#include <math.h>
double nextafter(double x, double y);
float nextafterf(float x, float y);
功能:返回x在趋向y方向上的下一个可表示的浮点数。
适用场景:
- 数值分析算法
- 浮点数误差分析
- 但STM32嵌入式开发中很少需要
6、输入输出冷门函数
6.1、 ungetc() – 将字符推回输入流
#include <stdio.h>
int ungetc(int c, FILE *stream);
STM32适用性:
// 在自定义解析器中可能有用
int parse_number(FILE *stream) {
int c = fgetc(stream);
if (!isdigit(c)) {
ungetc(c, stream); // 不是数字,放回流中
return –1;
}
// … 解析数字
}
注意事项:
- 在STM32上,FILE流通常对应UART或USB
- 推回字符的实现可能依赖缓冲区
- 在无缓冲或小缓冲区系统中可能不可靠
7、 实用但少用的工具函数
7.1、 div() / ldiv() / lldiv() – 同时计算商和余数
#include <stdlib.h>
div_t div(int numer, int denom);
ldiv_t ldiv(long numer, long denom);
lldiv_t lldiv(long long numer, long long denom);
优点:
- 一次调用同时获得商和余数
- 在某些架构上可能比分别计算更高效
STM32示例:
// 传统做法
int quotient = a / b;
int remainder = a % b;
// 使用div
div_t result = div(a, b);
// result.quot 是商
// result.rem 是余数
7.2、 bsearch() – 二分查找
#include <stdlib.h>
void *bsearch(const void *key, const void *base,
size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
虽然bsearch不是特别冷门,但在STM32嵌入式开发中,由于数据量通常不大,开发者更倾向于使用线性查找。
7.3、qsort -快速排序
void qsort(
void *base, // 待排序数组首地址
size_t nmemb, // 数组元素个数
size_t size, // 单个元素大小(字节)
int (*compar)(const void *, const void *) // 比较函数指针
);
qsort 是 stdlib 标准通用快排,STM32 裸机可直接调用,适配任意数据类型,依靠自定义回调函数实现升降序,嵌入式因栈开销大多手写排序替代。 示例:
#include <stdio.h>
#include <stdlib.h>
// 升序比较函数
int cmp_ascend(const void *a, const void *b)
{
return *(int*)a – *(int*)b;
}
// 降序比较函数
int cmp_descend(const void *a, const void *b)
{
return *(int*)b – *(int*)a;
}
int main(void)
{
int arr[] = {5, 2, 9, 1, 5, 6};
int num = sizeof(arr) / sizeof(int);
// 升序排序
qsort(arr, num, sizeof(int), cmp_ascend);
// 降序排序
// qsort(arr, num, sizeof(int), cmp_descend);
// 打印结果
for(int i=0; i<num; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
8、 在STM32项目中使用的建议
8.1 、需要考虑的因素
代码体积
- 使用冷门函数可能引入不常用的库代码
- 使用arm-none-eabi-size工具检查影响
执行效率
- 某些冷门函数可能针对特定场景优化
- 但通用函数通常已经过充分优化
可读性和可维护性
- 团队成员可能不熟悉这些函数
- 增加代码理解成本
可移植性
- 标准函数在不同编译器间行为一致
- 但某些冷门函数可能不是所有编译器都完全支持
8.2、 推荐使用场景
| memccpy() | 解析分隔符分隔的数据 | strchr() + memcpy() |
| div() | 需要同时获取商和余数的整数运算 | 分别使用/和% |
| bsearch() | 大型静态查找表 | 线性查找或自定义哈希 |
| strxfrm() | 多语言UI的字符串排序 | 固定编码的strcmp() |
9、 结论
STM32开发中确实存在许多可以正常调用但极少使用的C标准冷门函数。这些函数在某些特定场景下可能提供优势,但在大多数嵌入式应用中,使用更常见、更易理解的函数通常是更好的选择。
关键建议:
在资源受限的STM32环境中,代码的清晰性和可维护性往往比微小的性能优化更重要。只有在经过充分测试和评估后,才应考虑使用这些冷门函数。
二、代码示例
1、示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ====================== 示例1:int 数组排序 + 查找 ======================
// int 升序比较函数(给 qsort / bsearch 共用)
int cmp_int(const void* a, const void* b)
{
return *(int*)a – *(int*)b;
}
void test_int_qsort_bsearch(void)
{
int arr[] = { 12, 3, 56, 7, 22, 49 };
int num = sizeof(arr) / sizeof(arr[0]);
int key = 22; // 要查找的值
int* find_p;
printf("===== 示例1:int 数组 =====\\n");
// 1. 先排序(必须排序!bsearch 只支持有序数组)
qsort(arr, num, sizeof(int), cmp_int);
printf("排序后:");
for (int i = 0; i < num; i++) {
printf("%d ", arr[i]);
}
printf("\\n");
// 2. 二分查找
find_p = (int*)bsearch(&key, arr, num, sizeof(int), cmp_int);
if (find_p != NULL) {
printf("找到元素:%d\\n\\n", *find_p);
}
else {
printf("未找到元素\\n\\n");
}
}
// ====================== 示例2:结构体数组(嵌入式常用) ======================
// 设备信息结构体
typedef struct {
int id; // 设备ID(按此字段排序)
char name[16]; // 设备名
} Device_T;
// 按 id 升序比较
int cmp_device(const void* a, const void* b)
{
return ((Device_T*)a)->id – ((Device_T*)b)->id;
}
void test_struct_qsort_bsearch(void)
{
// 乱序设备列表
Device_T devs[] = {
{5, "LED"},
{2, "OLED"},
{8, "KEY"},
{1, "SENSOR"},
{4, "MOTOR"}
};
int num = sizeof(devs) / sizeof(devs[0]);
Device_T key = { 4, "" }; // 要查找的设备ID=4
Device_T* find_p;
printf("===== 示例2:结构体数组 =====\\n");
// 1. 排序
qsort(devs, num, sizeof(Device_T), cmp_device);
printf("按ID排序后:\\n");
for (int i = 0; i < num; i++) {
printf("ID:%d Name:%s\\n", devs[i].id, devs[i].name);
}
// 2. 查找
find_p = (Device_T*)bsearch(&key, devs, num, sizeof(Device_T), cmp_device);
if (find_p != NULL) {
printf("\\n找到设备:ID=%d, Name=%s\\n", find_p->id, find_p->name);
}
else {
printf("\\n未找到设备\\n");
}
}
// ====================== 主函数 ======================
int main(void)
{
test_int_qsort_bsearch();
test_struct_qsort_bsearch();
return 0;
}
2、运行结果
===== 示例1:int 数组 =====
排序后:3 7 12 22 49 56
找到元素:22
===== 示例2:结构体数组 =====
按ID排序后:
ID:1 Name:SENSOR
ID:2 Name:OLED
ID:4 Name:MOTOR
ID:5 Name:LED
ID:8 Name:KEY
找到设备:ID=4, Name=MOTOR
D:\\user\\01417804\\桌面\\新建文件夹\\Project1\\x64\\Debug\\Project1.exe (进程 30176)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .





