# C 语言字符串库函数参考 — STM32 嵌入式开发指南

聚焦 STM32 嵌入式开发场景,整理 <string.h><stdio.h> 中常用字符串操作函数的分类、原型、使用场景与安全注意事项。

适用平台 :STM32 全系列

标准库 :Newlib / Newlib-nano

核心原则 :安全 · 轻量 · 可重入

规范标准:C99 / POSIX


一、长度查询类

获取字符串长度是 STM32 数据处理最频繁的操作,注意空指针与缓冲区边界。

1.1 strlen

c 复制代码
size_t strlen(const char *s);

返回字符串长度(不含 \0)。

⚠️ STM32 注意 :空指针、无限读取风险。如果 s 指向的内存没有 \0,会导致越界读取。

示例:

c 复制代码
char buf[] = "Hello STM32";
size_t len = strlen(buf);  // len = 11

1.2 strnlen

c 复制代码
size_t strnlen(const char *s, size_t maxlen);

限制最大长度的 strlen,最多读取 maxlen 个字符。

STM32 推荐:首选函数,防止无限读取导致 HardFault。

示例:

c 复制代码
char buf[64];
// 从 UART 接收数据后,安全获取长度
size_t len = strnlen(buf, sizeof(buf) - 1);

二、比较类

字符串比较广泛应用于指令解析(如 AT 命令)、协议帧校验等场景。

2.1 strcmp

c 复制代码
int strcmp(const char *s1, const char *s2);

逐字符比较(区分大小写),返回 0 表示相等。

⚠️ STM32 注意 :无边界检查,注意输入长度。两个字符串都必须以 \0 结尾。

示例:

c 复制代码
if (strcmp(cmd, "RESET") == 0) {
    system_reset();
}

2.2 strncmp

c 复制代码
int strncmp(const char *s1, const char *s2, size_t n);

仅比较前 n 个字符。

STM32 推荐:首选函数,防溢出。比较 AT 命令前缀时尤其有用。

示例:

c 复制代码
// 安全比较 AT 命令前缀
if (strncmp(cmd, "AT+", 3) == 0) {
    process_at_command(cmd + 3);
}

// 比较响应结果(固定长度)
if (strncmp(rx_buf, "OK\r\n", 4) == 0) {
    // 命令执行成功
}

2.3 strcasecmp

c 复制代码
int strcasecmp(const char *s1, const char *s2);

不区分大小写比较(POSIX 扩展,Newlib 支持)。

⚠️ STM32 注意:部分库支持(Newlib 有),注意重入问题。非标准 C99 函数。

示例:

c 复制代码
if (strcasecmp(method, "GET") == 0) {
    handle_get_request();
}

三、复制 / 填充类

内存操作函数在 STM32 中极其高频,用于数据缓冲、协议帧构造、外设 DMA 传输等。

3.1 strcpy

c 复制代码
char *strcpy(char *dest, const char *src);

复制字符串(含 \0)。

绝对禁止:无边界检查,在 STM32 中极易导致缓冲区溢出,引发 HardFault 或内存越界。

反面教材(不要用):

c 复制代码
// ❌ 危险!dest 溢出无感知
char dest[8];
strcpy(dest, "Hello World!");  // 溢出!

3.2 strncpy

c 复制代码
char *strncpy(char *dest, const char *src, size_t n);

复制最多 n 个字符到 dest

⚠️ 谨慎使用 :不保证 \0 终止。如果 src 长度 >= ndest 将不会以 \0 结尾。

示例:

c 复制代码
char buf[32];
strncpy(buf, src, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';  // 必须手动补终止符!

3.3 memcpy

c 复制代码
void *memcpy(void *dest, const void *src, size_t n);

内存块复制(不关心 \0),效率最高。

STM32 推荐:高频使用,效率最高。适用于已知长度的二进制数据复制。

示例:

c 复制代码
// 复制已知长度的数据
uint8_t rx_data[16];
memcpy(rx_data, dma_buffer, 16);

// 复制字符串(已知长度时比 strncpy 更安全)
memcpy(buf, src, len);
if (len < sizeof(buf)) buf[len] = '\0';

3.4 memset

c 复制代码
void *memset(void *s, int c, size_t n);

内存块填充(常用于清空缓冲区)。

STM32 推荐:DMA 清空、结构体初始化必备。

示例:

c 复制代码
// 清空结构体
SensorData_t sensor;
memset(&sensor, 0, sizeof(sensor));

// 清空 UART 接收缓冲区
memset(rx_buf, 0, sizeof(rx_buf));

四、连接 / 拼接类

字符串拼接在构建协议报文(如 MQTT、HTTP、Modbus)中非常常见。

4.1 strcat

c 复制代码
char *strcat(char *dest, const char *src);

拼接字符串。

绝对禁止 :极易溢出。dest 必须有足够的剩余空间,但无法检查。

反面教材(不要用):

c 复制代码
// ❌ 危险!
char buf[16] = "Temp:";
strcat(buf, "123.456789");  // 可能溢出

4.2 strncat

c 复制代码
char *strncat(char *dest, const char *src, size_t n);

最多拼接 n 个字符。

⚠️ 谨慎使用 :需确保 dest 有剩余空间,且本身已 \0 终止。

示例:

c 复制代码
char buf[32] = "Sensor";
strncat(buf, "_Data", sizeof(buf) - strlen(buf) - 1);

4.3 snprintf

c 复制代码
int snprintf(char *s, size_t n, const char *fmt, ...);

格式化拼接(强烈推荐)。

STM32 必备 :C99 标准,安全灵活。替代所有 strcat/strcpy/sprintf

示例:

c 复制代码
// 构建传感器数据上报报文(JSON)
char msg[64];
int len = snprintf(msg, sizeof(msg),
    "{\"temp\":%.1f,\"hum\":%.1f,\"id\":\"%s\"}",
    temperature, humidity, device_id
);

// 构建 MQTT Topic
char topic[48];
snprintf(topic, sizeof(topic), "sensor/%s/data", device_id);

// 构建 AT 命令
char at_cmd[32];
snprintf(at_cmd, sizeof(at_cmd), "AT+SEND=%d,%s\r\n", data_len, payload);

五、查找 / 搜索类

解析协议帧、提取关键字段时经常使用查找函数。

5.1 strchr

c 复制代码
char *strchr(const char *s, int c);

查找字符首次出现。

示例:

c 复制代码
char *colon = strchr(http_header, ':');
if (colon) {
    *colon = '\0';  // 分隔键和值
    char *value = colon + 1;
}

5.2 strrchr

c 复制代码
char *strrchr(const char *s, int c);

查找字符最后一次出现。

示例:

c 复制代码
// 取文件扩展名(最后一次出现的 '.')
char *ext = strrchr(filename, '.');
if (ext && strcmp(ext, ".bin") == 0) {
    // 扩展名为 .bin,执行固件升级
}

5.3 strstr

c 复制代码
char *strstr(const char *haystack, const char *needle);

查找子串。

示例:

c 复制代码
// 定位帧结束符 \r\n
char *p = strstr(rx_buf, "\r\n");
if (p) {
    size_t frame_len = p - rx_buf;
    // 处理完整帧...
}

// 检查响应中是否包含 "ERROR"
if (strstr(response, "ERROR") != NULL) {
    handle_error();
}

5.4 strpbrk

c 复制代码
char *strpbrk(const char *s, const char *accept);

查找第一个出现在 accept 集合中的字符。

示例:

c 复制代码
// 查找第一个逗号或分号(分隔符)
char *delim = strpbrk(line, ",;");
if (delim) *delim = '\0';  // 截断字符串

5.5 memchr

c 复制代码
void *memchr(const void *s, int c, size_t n);

内存中查找字符(有限长度)。

示例:

c 复制代码
// 在二进制数据中查找分隔符
uint8_t *p = memchr(rx_buffer, 0xAA, rx_len);
if (p) {
    size_t offset = p - rx_buffer;
}

// 安全获取字符串长度(二进制+文本混合数据)
size_t blen = (char*)memchr(buf, '\0', sizeof(buf)) - buf;

六、转换类

数值与字符串之间的转换是嵌入式开发中最容易出错的环节之一。

6.1 atoi

c 复制代码
int atoi(const char *nptr);

字符串转 int

不推荐:无错误处理。非法输入时行为未定义,返回 0 无法区分"输入为0"和"转换失败"。

反面教材(不要用):

c 复制代码
// ❌ 无法判断 "abc" 和 "0"
int val = atoi("abc");  // val = 0,但不知道是错误

6.2 atol

c 复制代码
long atol(const char *nptr);

字符串转 long

不推荐 :同 atoi,无错误处理。


6.3 strtol

c 复制代码
long strtol(const char *nptr, char **endptr, int base);

字符串转 long,支持进制转换 + 错误检测(强烈推荐)。

STM32 推荐 :通过 endptr 精确判断转换位置,检测非法字符,支持 2/8/10/16 进制。

示例:

c 复制代码
// 带错误检查的字符串转整数
char *end;
long val = strtol(str, &end, 10);
if (*end != '\0') {
    // 转换不完整,str 中包含非数字字符
    printf("Invalid character: %c\n", *end);
}

// 16 进制字符串转整数(如解析地址)
uint32_t addr = strtol("0x08000000", NULL, 16);  // addr = 0x08000000

// 2 进制字符串转整数
int flags = strtol("1010", NULL, 2);  // flags = 10

6.4 sprintf

c 复制代码
int sprintf(char *s, const char *fmt, ...);

格式化输出到字符串。

危险:无边界检查,缓冲区溢出风险。嵌入式中禁止使用。

反面教材(不要用):

c 复制代码
// ❌ 危险!
char buf[8];
int x = 12345;
sprintf(buf, "%d", x);  // 写入 "12345"(5字符),但如果 x 很大就溢出

6.5 snprintf

c 复制代码
int snprintf(char *s, size_t n, const char *fmt, ...);

安全版 sprintf必备)。

STM32 必备:始终限制写入长度,返回值可判断是否截断。

示例:

c 复制代码
// 浮点数格式化(STM32 传感器常用)
char volt_str[16];
snprintf(volt_str, sizeof(volt_str), "%.2fV", 3.14159);  // "3.14V"

// 带截断检测的格式化
char buf[10];
int n = snprintf(buf, sizeof(buf), "Temperature: %d", 250);
if (n >= sizeof(buf)) {
    // 输出被截断了
}

七、实用工具类

这些函数在日常嵌入式开发中虽然不是最常用,但特定场景下非常高效。

7.1 strtok

c 复制代码
char *strtok(char *str, const char *delim);

按分隔符切分字符串(非线程安全)。

⚠️ STM32 注意 :会修改原字符串(将分隔符替换为 \0),且非线程安全。FreeRTOS 下请使用 strtok_r

示例:

c 复制代码
// 解析 CSV 数据
char line[] = "12.5,68.3,1013.2";
char *token = strtok(line, ",");
while (token) {
    float val = atof(token);
    process(val);
    token = strtok(NULL, ",");
}

7.2 strtok_r

c 复制代码
char *strtok_r(char *str, const char *delim, char **saveptr);

线程安全的 strtok(GNU 扩展 / POSIX)。

STM32 推荐 :FreeRTOS 环境下使用。通过 saveptr 保存状态,可重入。

示例:

c 复制代码
// FreeRTOS 下安全解析
char line[] = "cmd,arg1,arg2";
char *saveptr;
char *cmd = strtok_r(line, ",", &saveptr);
char *arg1 = strtok_r(NULL, ",", &saveptr);
char *arg2 = strtok_r(NULL, ",", &saveptr);

7.3 strspn

c 复制代码
size_t strspn(const char *s, const char *accept);

返回 s 开头 accept 字符集的连续长度。

示例:

c 复制代码
// 跳过前导空白字符
const char *s = "   \t\nHello";
size_t offset = strspn(s, " \t\n");
const char *content = s + offset;  // 指向 "Hello"

// 验证字符串是否全为数字
if (strspn(input, "0123456789") == strlen(input)) {
    // 是纯数字字符串
}

7.4 strcspn

c 复制代码
size_t strcspn(const char *s, const char *reject);

返回 s 开头直到出现 reject 字符集的长度。

示例:

c 复制代码
// 查找第一个非数字字符的位置
const char *num_str = "12345abc";
size_t num_len = strcspn(num_str, "abcdefghijklmnopqrstuvwxyz");
// num_len = 5

// 获取一行文本(直到换行符)
size_t line_len = strcspn(buffer, "\r\n");

八、陷阱与实战技巧

STM32 资源受限,字符串处理不当极易引发难以调试的 Crash。

类别 问题 解决方案
⛔ 危险 strcpy / strcat / sprintf 无边界检查 全部替换为 strncpy / snprintf
⚠️ 注意 strncpy 不保证 \0 终止 手动 buf[sizeof(buf)-1] = '\0'
⛔ 危险 strtok 修改原字符串且非线程安全 FreeRTOS 用 strtok_r,或自实现
⚠️ 注意 Flash 字符串字面量不可修改 不要对字符串字面量调用 strtok / strcpy(目标不能是字面量)
🔵 性能 Newlib 标准库体积大 使用 Newlib-nano 或链接 --specs=nano.specs

安全宏定义模板

c 复制代码
/* stm32_string_utils.h */

// 安全的字符串拷贝宏(dst 必须是数组,不能是指针)
#define SAFE_STRCPY(dst, src) do { \
    strncpy((dst), (src), sizeof(dst) - 1); \
    (dst)[sizeof(dst) - 1] = '\0'; \
} while (0)

// 安全的字符串格式化宏(dst 必须是数组,不能是指针)
#define SAFE_SPRINTF(dst, fmt, ...) \
    snprintf((dst), sizeof(dst), fmt, ##__VA_ARGS__)

// 线程安全的 strtok 封装(FreeRTOS)
static inline char *safe_strtok(char *str, const char *delim, char **saveptr) {
    return strtok_r(str, delim, saveptr);
}

九、速查表

推荐函数(日常使用首选)

函数 用途 替代
strnlen 安全获取长度 strlen
strncmp 安全比较 strcmp
memcpy 内存块复制 strcpy
memset 清空/初始化 手动循环
snprintf 安全格式化 sprintf / strcat
strstr 子串搜索 手动查找
strchr 字符查找 手动查找
strtok_r 线程安全切分 strtok
strtol 带错误检查的转换 atoi / atol
strpbrk 多分隔符查找 多次 strchr
memchr 二进制数据查找 手动查找
memmove 重叠内存复制 memcpy(有重叠时)

绝对禁止

复制代码
strcpy  ·  strcat  ·  sprintf

谨慎使用

复制代码
strncpy(需手动补 \0) ·  strtok(非 RTOS) ·  atoi(无错误处理)

总结 :STM32 下字符串处理的核心原则是 ------ 优先 strn* 族、摒弃 str* 无边界函数、强制用 snprintf、配合 RTOS 时注意重入性。