用realloc改写C字符串拼接函数导致STM32硬故障求助
STM32中realloc实现字符串拼接触发HardFault问题排查与解决
我在网上找到一个字符串拼接函数,尝试将其else分支从calloc改为realloc实现以优化内存使用,但改写后STM32单片机频繁触发HardFault_Handler,原calloc版本无此问题。使用STM32CubeIDE调试未定位到根因,怀疑是内存泄漏或长度计算错误,寻求解决建议。
原拼接函数代码
void concatenateStrings(char **string, const char *stringToAdd) { // Reset *str if (*string != NULL && stringToAdd == NULL) { free(*string); *string = NULL; return; } // Initial copy if (*string == NULL) { *string = calloc(strlen(stringToAdd) + 1, sizeof(char)); memcpy(*string, stringToAdd, strlen(stringToAdd)); } else { // Append //WORKING char *tmp = NULL; uint16_t stringSize = strlen(*string); uint16_t stringToAddSize = strlen(stringToAdd); tmp = calloc(stringSize + 1, sizeof(char)); memcpy(tmp, *string, stringSize); *string = calloc(stringSize + stringToAddSize + 1, sizeof(char)); memcpy(*string, tmp, strlen(tmp)); memcpy(*string + stringSize, stringToAdd, stringToAddSize); free(tmp); //NOT WORKING /* char *tmp = NULL; uint16_t stringSize = strlen(*string); uint16_t stringToAddSize = strlen(stringToAdd); tmp = realloc(*string, (stringSize + stringToAddSize + 1) * sizeof(char)); //memset(tmp + stringSize, 0, (stringSize + stringToAddSize + 1) * sizeof(char) - stringSize * sizeof(char)); *string = tmp; memcpy(*string + stringSize, stringToAdd, stringToAddSize + 1); */ } }
编辑1:函数使用片段
char *stringBuffer; volatile uint8_t canReceivedValue1 = 0; char *stringBuffer; char *stringBufferMain; ... void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { ... uint16_t sizeForStringBuffer = snprintf(NULL, 0, "%d\r\n", canReceivedValue1); stringBuffer = (char *)(calloc(sizeForStringBuffer + 1, sizeof(char))); snprintf(stringBuffer, sizeForStringBuffer + 1, "%d\r\n", canReceivedValue1); concatenateStrings(&stringBufferMain, stringBuffer); //HAL_USART_Transmit(&husart6, (const uint8_t *)stringBuffer, (uint16_t)(strlen(stringBuffer)), 1000); free(stringBuffer); ... }
编辑2:STM32CubeIDE栈追踪截图

编辑3:最小复现示例
main.c
//main.c #include "main.h" #include <stdlib.h> #include <string.h> #include <stdio.h> #include "Queue.h" #include "CAN_Lib.h" #define PROBE_QUEUE_SIZE (200) #define MAX_ID (16) Queue canRxQueue; canStruct canStructure; volatile uint8_t receivedValue=0; uint8_t id=1; Queue *canRxQueues; char *strBuf=NULL; char *strBufMain=NULL; void concatenateStrings(char **string, const char *stringToAdd); int main(void) { //HAL init, clock init, peripherals inits (including CAN)... canRxQueues=(Queue *)malloc(sizeof(Queue)*16); //queues for data from devices for (uint8_t i=0; i<16; i++) { initQueue(canRxQueues+i, PROBE_QUEUE_SIZE); } startCAN(&canStructure); //start CAN HAL_TIM_Base_Start_IT(&htim10); //start timer that sends CAN messages after certain period of time while (1) {} } //function for receivement data from CAN bus void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { canGetMessageFifo0(&canStructure); //receive data from CAN bus (it's an array of 8 uint8_t values, something like rVal[8]={1,2,3,4,5,6,7,8}) uint8_t _id=(canStructure.rxHeader->ExtId); //id of device from which we receive data uint8_t rxLen=8; //we assume the received values require one byte to be stored for(uint8_t i=0; i<rxLen; i++) { //for every byte of received data... receivedValue=*(canStructure.dataReceived+i); //get received value from canStruct where it's stored enqueue((canRxQueues+(_id-1)), receivedValue); //put the variable into queue //DEBUG// if (_id==16) { //we want to debug data only from device with _id equal to 16 uint16_t strBufSize=snprintf(NULL, 0, "%d\r\n", receivedValue); //size of string to be added to main string strBuf=(char *)(calloc(strBufSize+1, sizeof(char))); //allocate space for strBuf snprintf(strBuf, strBufSize+1, "%d\r\n", receivedValue); //make string concatenateStrings(&strBufMain, strBuf); //add strBuf to strBufMain free(strBuf); //deallocate strBuf } //END OF DEBUG// } //DEBUG// if (_id==16) { //transfer concatenated string via USART_DMA HAL_USART_Transmit_DMA(&husart6, (const uint8_t *)strBufMain, (uint16_t)(strlen(strBufMain))); free(strBufMain); } //END OF DEBUG// } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance==TIM10) { //request data from device static uint8_t generalDataRequest[8]={0,0,0,0,0,0,0,0}; //data request command canSendMessage(&canStructure, generalDataRequest); //send data request } } void concatenateStrings(char **string, const char *stringToAdd) { // Reset *str if ( *string!=NULL && stringToAdd==NULL ) { free(*string); *string=NULL; return; } // Initial copy if (*string==NULL) { *string=calloc( strlen(stringToAdd)+1, sizeof(char) ); if (*string==NULL) { asm("NOP"); } memcpy(*string, stringToAdd, strlen(stringToAdd)); } else { // Append //WORKING char *tmp=NULL; uint16_t stringSize=strlen(*string); uint16_t stringToAddSize=strlen(stringToAdd); tmp=calloc( stringSize+1, sizeof(char) ); memcpy( tmp, *string, stringSize ); *string=calloc( stringSize+stringToAddSize+1, sizeof(char) ); memcpy( *string, tmp, strlen(tmp) ); memcpy( *string + stringSize, stringToAdd, stringToAddSize ); free(tmp); //NOT WORKING // char *tmp=NULL; // uint16_t stringSize=strlen(*string); // uint16_t stringToAddSize=strlen(stringToAdd); // // tmp=realloc(*string, (stringSize+stringToAddSize+1)*sizeof(char)); // if (tmp==NULL) { // asm("NOP"); // } // //memset(tmp+stringSize, 0, (stringSize+stringToAddSize+1)*sizeof(char)-stringSize*sizeof(char)); // memcpy(tmp + stringSize, stringToAdd, stringToAddSize+1); // *string=tmp; } }
Queue.h
//Queue.h #include <string.h> #include <stdint.h> #include <stdlib.h> typedef struct { int value; } intValue; typedef struct { int begin; int end; int currentLoad; int size; intValue *valueArray; } Queue; void initQueue(Queue *queue, int queueLimit); uint8_t enqueue(Queue *queue, int value); int dequeue(Queue *queue);
Queue.c
//Queue.c #include "Queue.h" void initQueue(Queue *queue, int queueLimit) { queue->begin=0; queue->end=0; queue->currentLoad=0; queue->size=queueLimit; queue->valueArray=(intValue *)calloc(queueLimit, sizeof(intValue)); memset(queue->valueArray, 0, queueLimit*sizeof(intValue)); } uint8_t enqueue(Queue *queue, int value) { if (queue->currentLoad<queue->size) { //if queue is not full... if (queue->end==queue->size) { queue->end=0; //end index circles back to the beginning of the queue if it surpasses the queue's size } queue->valueArray[queue->end].value=value; queue->end++; queue->currentLoad++; return 0; } else { return 1; //queue is full } } int dequeue(Queue *queue) { if (queue->currentLoad>0 || queue->end!=queue->begin) { //if queue is not empty... int value=queue->valueArray[queue->begin].value; memset(&queue->valueArray[queue->begin], 0, sizeof(intValue)); //free(&queue->valueArray[queue->begin]); could be used if valueArray's memory was allocated dynamically queue->begin = (queue->begin + 1) % queue->size; //it circles back to the beginning because of modulo (%) operation queue->currentLoad--; return value; } else { return -1; //queue is empty } }
问题根因分析
- 长度类型溢出:
strlen返回size_t(32位无符号),但你用uint16_t存储长度。当拼接后的字符串长度超过65535时,uint16_t会溢出,导致realloc申请的内存远小于实际需求,后续memcpy越界访问非法内存触发HardFault。 - realloc失败未正确处理:当
realloc返回NULL时,你仅执行asm("NOP"),未终止后续操作,导致后续memcpy访问NULL指针直接触发HardFault。 - 边界处理隐患:原
calloc版本依赖calloc的0初始化补全字符串终止符,realloc版本虽复制了终止符,但如果stringToAdd无合法终止符,会导致strlen计算错误,进而引发越界。
修正后的realloc版本代码
void concatenateStrings(char **string, const char *stringToAdd) { // 重置字符串 if (*string != NULL && stringToAdd == NULL) { free(*string); *string = NULL; return; } // 空输入直接返回 if (stringToAdd == NULL) { return; } // 首次初始化 if (*string == NULL) { size_t addLen = strlen(stringToAdd); *string = calloc(addLen + 1, sizeof(char)); if (*string == NULL) { // 内存分配失败,可添加断言或错误日志 return; } memcpy(*string, stringToAdd, addLen); } else { // 追加字符串 size_t stringSize = strlen(*string); size_t stringToAddSize = strlen(stringToAdd); size_t newTotalSize = stringSize + stringToAddSize + 1; char *tmp = realloc(*string, newTotalSize); if (tmp == NULL) { // realloc失败,保留原指针避免内存泄漏,返回 return; } // 追加新字符串(包含终止符) memcpy(tmp + stringSize, stringToAdd, stringToAddSize + 1); *string = tmp; } }
额外建议
- 统一使用
size_t存储字符串长度,避免溢出风险。 - 中断回调(如CAN接收回调)中避免频繁动态内存操作,长期运行易产生内存碎片,建议使用预先分配的固定大小缓冲区或内存池。
- 内存分配失败时添加明确的错误处理逻辑(如断言、日志),便于调试。
- 调试HardFault时,可查看
SCB->CFSR寄存器值,定位具体错误类型(如数据访问越界、空指针访问)。
内容的提问来源于stack exchange,提问作者inferjus
相关产品推荐
相关产品推荐

