为何我的可变参数vsscanf函数存在未定义行为?
问题分析:可变参数解析函数的未定义行为
我写了一个可变参数函数用于解析可能包含不同参数的字符串,当前场景下传入的字符串为"38400,8,N,1\n"。
解析函数实现
static int parse_response(const char *buff, char *format, ...){ uint16_t retval=0; va_list arg_ptr; va_start(arg_ptr, format); retval = vsscanf((const char*)buff, format, arg_ptr); va_end(arg_ptr); return retval; }
调用解析函数的实现
satel_return_typedef Satel_GetBaudrate(satel_typedef* Satel, uint32_t* Baudrate, uint32_t timeout_ms){ int Len; Len = printf_SLCommandString(Satel->tx_data_buffer, Satel->max_size, "SL%%B?"); uint8_t DataBits; char Parity; uint8_t StopBit; if(Len < 0 || Len > (int)Satel->max_size) { return R_API_ERROR; } if(Satel->Platform->Comm_Send(Satel->tx_data_buffer, Len, NULL)<0) return R_PLATFORM_ERROR; if(Satel_WaitForCommResponse(Satel,timeout_ms)==R_COMM_RESPONSE_VALUE){ parse_response((char*)Satel->rx_data_buffer,"%d,%d,%c,%d\n", Baudrate, &DataBits, &Parity, &StopBit); return R_OP_SUCCESSED; } return R_OP_ERROR; }
相关类型定义
typedef void (*Write_Enamod_Pin)(satel_power_typedef Config); typedef void (*Write_Service_Pin)(satel_service_typedef Config); typedef int32_t (*Send_Message)(const uint8_t *data_to_send, uint32_t bytes_to_send, void *custom); /* -1 IO Error*/ typedef int32_t (*Read_Message)(uint8_t *data_to_read, uint32_t bytes_to_read, void *custom); /* -1 IO Error*/ typedef uint32_t (*Get_TickCount)(void); typedef void (*Delay_ms)(uint32_t milisec); typedef struct{ Write_Enamod_Pin Power; Write_Service_Pin Service; Send_Message Comm_Send; Read_Message Comm_Read; Get_TickCount Get_TickCount; Delay_ms Delay_Ms; }satel_hw_typedef; typedef struct{ satel_state_typedef State; uint8_t* tx_data_buffer; uint8_t* rx_data_buffer; uint32_t max_size; satel_hw_typedef* Platform; }satel_typedef;
我知道这里存在未定义行为,但不清楚违反了哪条规则。
问题根源:格式说明符与参数类型不匹配
这段代码的未定义行为来自格式化输入函数的参数类型与格式说明符不匹配,违反了C标准中关于可变参数格式化函数的类型匹配要求:
- 格式字符串中的
%d要求对应的参数是int*类型,但调用时传入的:Baudrate是uint32_t*&DataBits是uint8_t*&StopBit是uint8_t*
- 这些类型和
int*不兼容,vsscanf会按照int*的方式去访问这些指针指向的内存,导致内存访问错误或数据解析异常,属于C标准明确规定的未定义行为(参考C17标准7.21.6.1第9款:如果任何参数不是对应转换说明符期望的类型,行为未定义)。
修复方案
需要使用匹配的格式说明符,或者先将值读取到int类型变量中,再转换为目标类型:
方案1:使用对应无符号类型的格式说明符
// 对于uint32_t使用%u,uint8_t使用%hhu parse_response((char*)Satel->rx_data_buffer,"%u,%hhu,%c,%hhu\n", Baudrate, &DataBits, &Parity, &StopBit);
注:%hhu是C99及以后支持的无符号char格式说明符;若uint32_t与unsigned int宽度不一致,需包含<inttypes.h>并使用%PRIu32替代%u。
方案2:先读取到int变量再转换
int baud, databits, stopbit; parse_response((char*)Satel->rx_data_buffer,"%d,%d,%c,%d\n", &baud, &databits, &Parity, &stopbit); *Baudrate = (uint32_t)baud; DataBits = (uint8_t)databits; StopBit = (uint8_t)stopbit;
内容的提问来源于stack exchange,提问作者EmbeddedMaker
相关产品推荐
相关产品推荐

