You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

用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栈追踪截图

HardFault栈追踪截图

编辑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
    }
}

问题根因分析

  1. 长度类型溢出:strlen返回size_t(32位无符号),但你用uint16_t存储长度。当拼接后的字符串长度超过65535时,uint16_t会溢出,导致realloc申请的内存远小于实际需求,后续memcpy越界访问非法内存触发HardFault。
  2. realloc失败未正确处理:当realloc返回NULL时,你仅执行asm("NOP"),未终止后续操作,导致后续memcpy访问NULL指针直接触发HardFault。
  3. 边界处理隐患:原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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 04:53:10