[单片机] x_strtok,安全分割函数 16进制整数转字符串
2026/9/14 11:47:38 网站建设 项目流程

引言

在嵌入式开发与底层 C 语言编程中,字符串处理和进制转换是两项非常基础却又高频使用的技能。无论是解析串口指令、处理通信协议报文,还是将二进制数据以可读形式输出,都离不开这两类操作。本文将从基础概念出发,先深入剖析strtok系列字符串分割函数的原理与注意事项,再介绍如何手写一个高效的 16 进制整数转字符串工具函数,最后通过一个综合实战示例串联起两个知识点,帮助读者从原理到实践完整掌握。

文章目录

    • 引言
  • 一、x_strtok
    • 1.定义
    • 2.原型
    • 3.说明
      • (1)包含的分割字符时
      • (2)返回值
      • (3)注意
    • 示例代码
  • 二、16进制整数转字符串
      • 1. 应用场景
      • 2. 实现思路
      • 3. 边界条件分析
      • 4. 改进后的带参数校验版本
    • 示例代码
    • 三、实战示例:解析并打印十六进制数据帧
    • 四、总结

在深入阅读正文之前,先通过下表快速了解strtok系列各变体的核心差异,便于按需选用:

函数线程安全可重入性是否破坏原字符串适用平台使用场景
strtok否(内部静态变量)C89/C99 标准库单线程、简单字符串分割,代码量最少
strtok_r是(调用方传入saveptrPOSIX(Linux/Unix)多线程环境下的字符串分割,需自行维护上下文
strtok_s是(调用方传入上下文)C11 标准(可选)/ Windows跨平台多线程分割,接口与strtok_r类似
x_strtok_s是(调用方传入lasts嵌入式 / 自定义实现不依赖平台扩展,可移植到裸机或 RTOS 环境

说明:strtok因使用静态变量保存分割状态,多线程并发调用会互相覆盖,导致结果错乱;strtok_rstrtok_sx_strtok_s均通过调用方提供的指针保存状态,因此线程安全且可重入。四者都会把分隔符替换为\0,从而破坏原字符串,若需保留原文请先拷贝一份。

一、x_strtok

1.定义

分解字符串为一组字符串。s为要分解的字符,delim为分隔符字符(如果传入字符串,则传入的字符串中每个字符均为分割符)。首次调用时,s指向要分解的字符串,之后再次调用要把s设成NULL。在头文件#include<string.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是分割完成之后的第一个字符串,剩余的字符串存储在一个静态变量中,因此多线程同时访问该静态变量时,则会出现错误。

示例代码

#include<string.h>#include"x_strtok.h"char*_x_strtok_s(char*s,constchar*delim,char**lasts,intskip_leading_delim){registerchar*spanp;registerintc,sc;char*tok;if(s==NULL&&(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(c==sc){if(skip_leading_delim){gotocont;}else{*lasts=s;s[-1]=0;return(s-1);}}}if(c==0){/* no non-delimiter characters */*lasts=NULL;return(NULL);}tok=s-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(c==0)s=NULL;elses[-1]=0;*lasts=s;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-9a-f),依次写入目标缓冲区,最后补上字符串结束符\0

3. 边界条件分析

上面的hex_to_str实现虽然简洁,但在实际工程中还需考虑以下边界情况:

  • len为 0 时:循环体不会执行,函数直接执行target[len] = '\0',即target[0] = '\0',此时target仅写入一个字符串结束符,得到一个空字符串。这在语义上是合理的,但要求调用方保证target至少能容纳 1 个字节。
  • sourcetarget为 NULL 时:函数会直接对空指针解引用,触发空指针异常(段错误)。在嵌入式或对健壮性要求较高的场景中,应在函数入口处增加参数校验,非法输入时提前返回。
  • len较大时:每个字节会转换为 2 个十六进制字符,加上末尾的'\0',共需2 * len + 1个字节。若调用方提供的target缓冲区小于该值,写入时就会发生缓冲区越界,破坏相邻内存数据,甚至引发安全漏洞。因此调用方必须确保缓冲区大小充足。

4. 改进后的带参数校验版本

针对上述边界条件,下面给出一个带参数校验的改进版本。函数增加返回值,用0表示成功、-1表示参数非法,便于调用方判断处理:

#include<stdint.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(source==NULL||target==NULL)return-1;/* 参数校验:目标缓冲区至少需要 2*len+1 字节(含结束符) */if(cap<(uint32_t)(2*len+1))return-1;/* len 为 0 时,循环不执行,仅写入结束符,得到空字符串 */for(i=0;i<len;i++){ddh=('0'+source[i]/16);ddl=('0'+source[i]%16);if(ddh>'9')ddh=(ddh+('a'-'9'-1));if(ddl>'9')ddl=(ddl+('a'-'9'-1));target[i*2]=ddh;target[i*2+1]=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(i=0;i<len;i++){ddh=('0'+source[i]/16);ddl=('0'+source[i]%16);if(ddh>'9')ddh=(ddh+('a'-'9'-1));if(ddl>'9')ddl=(ddl+('a'-'9'-1));target[i*2]=ddh;target[i*2+1]=ddl;}target[len]='\0';}

三、实战示例:解析并打印十六进制数据帧

下面综合运用x_strtok_shex_to_str,实现一个简单的数据帧解析与打印工具:从串口接收到的字符串中按逗号分割出若干十六进制片段,再将其转换为可读的十六进制字符串输出。

#include<stdio.h>#include<string.h>#include<stdint.h>#include<stdlib.h>#include<errno.h>#include"x_strtok.h"/* 目标缓冲区大小:最多 4 个字节,每个字节转成 2 个十六进制字符,再加结束符 */#defineMAX_BYTES4#defineHEX_STR_SIZE(MAX_BYTES*2+1)staticvoidhex_to_str(uint8_t*source,uint32_tlen,uint8_t*target){uint8_tddl,ddh;uint32_ti;for(i=0;i<len;i++){ddh=('0'+source[i]/16);ddl=('0'+source[i]%16);if(ddh>'9')ddh=(ddh+('a'-'9'-1));if(ddl>'9')ddl=(ddl+('a'-'9'-1));target[i*2]=ddh;target[i*2+1]=ddl;}target[len]='\0';}intmain(void){charframe[]="0x3A,0x1F,0x2C,0x4D";char*token=NULL;char*saveptr=NULL;uint8_tbytes[MAX_BYTES];uint8_thex_str[HEX_STR_SIZE];intidx=0;token=x_strtok_s(frame,",",&saveptr);while(token!=NULL){char*endptr=NULL;longval;/* 检查是否超出缓冲区容量,防止数组越界写入 */if(idx>=MAX_BYTES){printf("Error: too many tokens, buffer overflow avoided.\n");break;}/* 清空 errno,以便区分 strtol 是否因溢出而失败 */errno=0;val=strtol(token,&endptr,16);/* 转换失败:没有任何有效数字被解析 */if(endptr==token){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(errno==ERANGE){printf("Error: token \"%s\" out of range.\n",token);break;}/* 转换失败:数值超出 uint8_t 可表示范围(0~255) */if(val<0||val>0xFF){printf("Error: token \"%s\" out of uint8_t range.\n",token);break;}bytes[idx++]=(uint8_t)val;token=x_strtok_s(NULL,",",&saveptr);}/* 只有完整解析出全部字节时才执行转换与打印 */if(idx==MAX_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 语言开发中的字符串处理效率。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询