资讯动态

[单片机] x_strtok,安全分割函数 16进制整数转字符串

发布时间:2026/9/14 11:47:51 来源:尧图企业网站定制
引言在嵌入式开发与底层 C 语言编程中字符串处理和进制转换是两项非常基础却又高频使用的技能。无论是解析串口指令、处理通信协议报文还是将二进制数据以可读形式输出都离不开这两类操作。本文将从基础概念出发先深入剖析strtok系列字符串分割函数的原理与注意事项再介绍如何手写一个高效的 16 进制整数转字符串工具函数最后通过一个综合实战示例串联起两个知识点帮助读者从原理到实践完整掌握。文章目录引言一、x_strtok1.定义2.原型3.说明1包含的分割字符时2返回值3注意示例代码二、16进制整数转字符串1. 应用场景2. 实现思路3. 边界条件分析4. 改进后的带参数校验版本示例代码三、实战示例解析并打印十六进制数据帧四、总结在深入阅读正文之前先通过下表快速了解strtok系列各变体的核心差异便于按需选用函数线程安全可重入性是否破坏原字符串适用平台使用场景strtok否内部静态变量否是C89/C99 标准库单线程、简单字符串分割代码量最少strtok_r是调用方传入saveptr是是POSIXLinux/Unix多线程环境下的字符串分割需自行维护上下文strtok_s是调用方传入上下文是是C11 标准可选/ Windows跨平台多线程分割接口与strtok_r类似x_strtok_s是调用方传入lasts是是嵌入式 / 自定义实现不依赖平台扩展可移植到裸机或 RTOS 环境说明strtok因使用静态变量保存分割状态多线程并发调用会互相覆盖导致结果错乱strtok_r、strtok_s与x_strtok_s均通过调用方提供的指针保存状态因此线程安全且可重入。四者都会把分隔符替换为\0从而破坏原字符串若需保留原文请先拷贝一份。一、x_strtok1.定义分解字符串为一组字符串。s为要分解的字符delim为分隔符字符如果传入字符串则传入的字符串中每个字符均为分割符。首次调用时s指向要分解的字符串之后再次调用要把s设成NULL。在头文件#includestring.h中。2.原型char *strtok(char s[], const char *delim);3.说明1包含的分割字符时当strtok()在参数s的字符串中发现参数delim中包含的分割字符时,则会将该字符改为\0 字符。在第一次调用时strtok()必需给予参数s字符串往后的调用则将参数s设置成NULL。每次调用成功则返回指向被分割出片段的指针。2返回值从s开头开始的一个个被分割的串。当s中的字符查找到末尾时返回NULL。如果查找不到delim中的字符时返回当前strtok的字符串的指针。所有delim中包含的字符都会被滤掉并将被滤掉的地方设为一处分割的节点。3注意需要注意的是使用该函数进行字符串分割时会破坏被分解字符串的完整调用前和调用后的s已经不一样了。第一次分割之后原字符串str是分割完成之后的第一个字符串剩余的字符串存储在一个静态变量中因此多线程同时访问该静态变量时则会出现错误。示例代码#includestring.h#includex_strtok.hchar*_x_strtok_s(char*s,constchar*delim,char**lasts,intskip_leading_delim){registerchar*spanp;registerintc,sc;char*tok;if(sNULL(s*lasts)NULL)return(NULL);/* * Skip (span) leading delimiters (s strspn(s, delim), sort of). */cont:c*s;for(spanp(char*)delim;(sc*spanp)!0;){if(csc){if(skip_leading_delim){gotocont;}else{*lastss;s[-1]0;return(s-1);}}}if(c0){/* no non-delimiter characters */*lastsNULL;return(NULL);}toks-1;/* * Scan token (scan for delimiters: s strcspn(s, delim), sort of). * Note that delim must have one NUL; we stop if we see that, too. */for(;;){c*s;spanp(char*)delim;do{if((sc*spanp)c){if(c0)sNULL;elses[-1]0;*lastss;return(tok);}}while(sc!0);}/* NOTREACHED */}char*x_strtok_s(char*s,constchar*delim,char**lasts){return_x_strtok_s(s,delim,lasts,1);}#ifndef_X_STRTOK_H_#define_X_STRTOK_H_char*x_strtok_s(char*s,constchar*delim,char**lasts);#endif// _X_STRTOK_H_二、16进制整数转字符串1. 应用场景在嵌入式日志输出、调试信息打印、蓝牙或网络数据帧展示等场景中经常需要把原始字节流转换为可读的十六进制字符串。例如将传感器采集到的温度、湿度原始数据以0x3A 0x1F的形式打印出来便于开发者直观核对。2. 实现思路核心思路是逐字节处理将每个字节的高 4 位和低 4 位分别取出映射为对应的十六进制字符0-9、a-f依次写入目标缓冲区最后补上字符串结束符\0。3. 边界条件分析上面的hex_to_str实现虽然简洁但在实际工程中还需考虑以下边界情况len为 0 时循环体不会执行函数直接执行target[len] \0即target[0] \0此时target仅写入一个字符串结束符得到一个空字符串。这在语义上是合理的但要求调用方保证target至少能容纳 1 个字节。source或target为 NULL 时函数会直接对空指针解引用触发空指针异常段错误。在嵌入式或对健壮性要求较高的场景中应在函数入口处增加参数校验非法输入时提前返回。len较大时每个字节会转换为 2 个十六进制字符加上末尾的\0共需2 * len 1个字节。若调用方提供的target缓冲区小于该值写入时就会发生缓冲区越界破坏相邻内存数据甚至引发安全漏洞。因此调用方必须确保缓冲区大小充足。4. 改进后的带参数校验版本针对上述边界条件下面给出一个带参数校验的改进版本。函数增加返回值用0表示成功、-1表示参数非法便于调用方判断处理#includestdint.h/** * brief 将字节数组转换为十六进制字符串 * param source 源字节数组指针 * param len 源字节数 * param target 目标缓冲区指针 * param cap 目标缓冲区容量字节数 * return 0 成功-1 参数非法source/target 为 NULL或缓冲区容量不足 */inthex_to_str(uint8_t*source,uint32_tlen,uint8_t*target,uint32_tcap){uint8_tddl,ddh;uint32_ti;/* 参数校验空指针直接返回 */if(sourceNULL||targetNULL)return-1;/* 参数校验目标缓冲区至少需要 2*len1 字节含结束符 */if(cap(uint32_t)(2*len1))return-1;/* len 为 0 时循环不执行仅写入结束符得到空字符串 */for(i0;ilen;i){ddh(0source[i]/16);ddl(0source[i]%16);if(ddh9)ddh(ddh(a-9-1));if(ddl9)ddl(ddl(a-9-1));target[i*2]ddh;target[i*21]ddl;}target[len*2]\0;return0;}说明改进后的版本通过cap参数显式传入目标缓冲区容量在写入前完成容量校验从根本上避免越界风险同时增加空指针检查使函数在异常输入下也能安全返回。调用方在使用时应传入sizeof(target_buf)作为cap并检查返回值是否为0。示例代码staticvoidhex_to_str(uint8_t*source,uint32_tlen,uint8_t*target){uint8_tddl,ddh;uint32_ti;for(i0;ilen;i){ddh(0source[i]/16);ddl(0source[i]%16);if(ddh9)ddh(ddh(a-9-1));if(ddl9)ddl(ddl(a-9-1));target[i*2]ddh;target[i*21]ddl;}target[len]\0;}三、实战示例解析并打印十六进制数据帧下面综合运用x_strtok_s与hex_to_str实现一个简单的数据帧解析与打印工具从串口接收到的字符串中按逗号分割出若干十六进制片段再将其转换为可读的十六进制字符串输出。#includestdio.h#includestring.h#includestdint.h#includestdlib.h#includeerrno.h#includex_strtok.h/* 目标缓冲区大小最多 4 个字节每个字节转成 2 个十六进制字符再加结束符 */#defineMAX_BYTES4#defineHEX_STR_SIZE(MAX_BYTES*21)staticvoidhex_to_str(uint8_t*source,uint32_tlen,uint8_t*target){uint8_tddl,ddh;uint32_ti;for(i0;ilen;i){ddh(0source[i]/16);ddl(0source[i]%16);if(ddh9)ddh(ddh(a-9-1));if(ddl9)ddl(ddl(a-9-1));target[i*2]ddh;target[i*21]ddl;}target[len]\0;}intmain(void){charframe[]0x3A,0x1F,0x2C,0x4D;char*tokenNULL;char*saveptrNULL;uint8_tbytes[MAX_BYTES];uint8_thex_str[HEX_STR_SIZE];intidx0;tokenx_strtok_s(frame,,,saveptr);while(token!NULL){char*endptrNULL;longval;/* 检查是否超出缓冲区容量防止数组越界写入 */if(idxMAX_BYTES){printf(Error: too many tokens, buffer overflow avoided.\n);break;}/* 清空 errno以便区分 strtol 是否因溢出而失败 */errno0;valstrtol(token,endptr,16);/* 转换失败没有任何有效数字被解析 */if(endptrtoken){printf(Error: invalid hex token \%s\.\n,token);break;}/* 转换失败token 尾部存在非法字符如 0xZZ 中的 Z */if(*endptr!\0){printf(Error: trailing garbage in token \%s\.\n,token);break;}/* 转换失败数值超出 long 范围errno 被置为 ERANGE */if(errnoERANGE){printf(Error: token \%s\ out of range.\n,token);break;}/* 转换失败数值超出 uint8_t 可表示范围0~255 */if(val0||val0xFF){printf(Error: token \%s\ out of uint8_t range.\n,token);break;}bytes[idx](uint8_t)val;tokenx_strtok_s(NULL,,,saveptr);}/* 只有完整解析出全部字节时才执行转换与打印 */if(idxMAX_BYTES){hex_to_str(bytes,idx,hex_str);printf(Parsed hex string: %s\n,hex_str);}else{printf(Parsing failed, only %d/%d bytes valid.\n,idx,MAX_BYTES);}return0;}四、总结本文从strtok系列函数的基本定义、原型与注意事项出发梳理了字符串分割的底层机制随后介绍了 16 进制整数转字符串的常见实现思路并给出了可直接复用的工具函数最后通过一个综合实战示例演示了如何将两者结合用于实际的数据帧解析与格式化输出。掌握这两个基础工具能显著提升嵌入式与 C 语言开发中的字符串处理效率。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价