在 C 中连接字符串的最谨慎的方法

Most carefull way to concatenate strings in C

我的固件在每 10 秒一个周期内将字符串移动到输出(SD 卡上的 csv 文件)。问题是字符串有时会以不确定的方式改变添加不应该存在的值或将 space 放在中间。这与 sprintf 函数或该字符串的动态分配内存有关吗?

void archPolling()
{

    double archCountVal[200];
    float archDataVal[100];
    char *FilStringMeas = malloc(sizeof(char) * 2048);
    char *FilArchive = malloc(sizeof(char) * 4096);
    vArchEvent eventArch = STATE_POLLING;
    unsigned char CiphCRC[5];
    FIL FilData;
    UINT bw;
    int queueSize = 0;

    if ( xSemaphoreTake( MutexMeasurment, 200 ) == pdTRUE)
    {
        eventArch = STATE_COLLECT;
    }

    if (eventArch == STATE_COLLECT)
    {

        while (uxQueueMessagesWaiting(xDataQueue) > 0)
        {
            xQueueReceive(xDataQueue, &archDataVal[queueSize], 0);
            queueSize++;
        }
        xSemaphoreGive(MutexMeasurment);

        if (queueSize > 0 && timerFlag == 1)
            eventArch = STATE_FORM;
        else
            eventArch = STATE_POLLING;
    }

    if (eventArch == STATE_FORM)
    {
        //portENTER_CRITICAL();
        HAL_RTC_GetTime(&RtcHandle, &RTCTimeArch, FORMAT_BIN);
        HAL_RTC_GetDate(&RtcHandle, &RTCDateArch, FORMAT_BIN);
        sprintf(FilArchive, "%02d-%02d-%02d,%02d:%02d:%02d,1", RTCDateArch.Date, RTCDateArch.Month, RTCDateArch.Year, RTCTimeArch.Hours, RTCTimeArch.Minutes, RTCTimeArch.Seconds);
        sprintf(FilStringMeas, ",");
        for (int i = 0; i < queueSize; i++)
        {
            sprintf(FilStringMeas, "%s%f,", FilStringMeas, archDataVal[i]);
        }

        strcat(FilArchive, FilStringMeas);
        archCRC((BYTE *) FilArchive, strlen(FilArchive), CiphCRC);
        strcat(FilArchive, (char *) CiphCRC);
        strcat(FilArchive, "\n");
        //portEXIT_CRITICAL();
        eventArch = STATE_SYNC;
    }

    if (eventArch == STATE_SYNC)
    {
        f_open(&FilData, "0:55AD001.csv", FA_OPEN_EXISTING | FA_WRITE);
        f_lseek(&FilData, f_size(&FilData));
        f_write(&FilData, FilArchive, strlen(FilArchive) * sizeof(char), &bw);
        f_close(&FilData);
        timerFlag = 0;
        eventArch = STATE_POLLING;
    }

    free(FilStringMeas);
    free(FilArchive);
}

编辑:错误输出示例

0,0,1,3.512586,42.960911,,46.487427,24.501009,1.512586,27.498940,40.960911,36.598400,11.039062,9.401555,25.498940,42.487427,20.501009,7.512586,17.401556,36.960911,32.598400,7.039061,5.512586,31.498940,48.487427,16.501009,5.039061,13.401555,29.498940,46.487427,24.501009,1.512586,27.498940,44.487427,36.598400,11.039062,9.401555,38.960911,42.487427,20.501009,7.512586,33.498940,36.960911,32.598400,7.039061,15.401555,31.498940,48.487427,16.501009,3.512586,13.401555,42.960911,38.598400,3.039061,1.512586,27.498940,44.487427,22.501009,11.09062,AAAA

0,0,1,34.471630,6.303817,15,15.528328,45.382984,32.471630,6.617044,4.303817,29.472881,47.696175,16.527170,4.617044,11.528328,41.382984,38.471630,24.527170,0.303817,25.472881,43.696175,36.471630,10.617044,17.528328,37.382984,41.696175,20.527170,8.617044,15.528328,45.382984,32.471630,6.617044,13.528328,29.472881,47.696175,16.527170,2.303817,11.528328,41.382984,38.471630,12.617044,0.303817,25.472881,43.696175,22.527170,10.617044,17.528328,37.382984,34.471630,20.527170,6.303817,31.472881,39.696175,32.471630,6.617044,13.528328,43.382984,496175,AAAA

我看到您使用 sprintf(dest, "%s...", dest, ...); 来连接您的字符串。我刚刚再次阅读了 sprintf 的联机帮助页,如果没有明确禁止,则不会明确允许。

就我而言,我绝不会那样做。即使它有效,你要求 printf 机器明确地复制一个(越来越长的)字符串到它自己。

而不是:

    sprintf(FilArchive, "%02d-%02d-%02d,%02d:%02d:%02d,1", RTCDateArch.Date, RTCDateArch.Month, RTCDateArch.Year, RTCTimeArch.Hours, RTCTimeArch.Minutes, RTCTimeArch.Seconds);
    sprintf(FilStringMeas, ",");
    for (int i = 0; i < queueSize; i++)
    {
        sprintf(FilStringMeas, "%s%f,", FilStringMeas, archDataVal[i]);
    }
    strcat(FilArchive, FilStringMeas);

我愿意

    sprintf(FilArchive, "%02d-%02d-%02d,%02d:%02d:%02d,1", RTCDateArch.Date, RTCDateArch.Month, RTCDateArch.Year, RTCTimeArch.Hours, RTCTimeArch.Minutes, RTCTimeArch.Seconds);
    strcat(FilArchive, ",");
    for (int i = 0; i < queueSize; i++)
    {
        sprintf(FilStringMeas, %f,", archDataVal[i]);
        strcat(FilArchive, FilStringMeas);
    }

我会测试缓冲区大小。

我认为 Jack Whitman 指出的问题是造成虚假输出的原因:您不应将要打印的字符串作为参数传递给 sprintf。另一个潜在风险是缓冲区溢出,sprintfstrcat 无法防范。 (好吧,不能防备,因为他们不知道缓冲区有多大。)

您构建一个字符串,然后将其附加到一个文件中。解决您的问题的一种方法是不创建中间字符串,而是将部分格式化的字符串直接附加到文件中。

另一个解决方案是跟踪写入的字符数。此信息由 printf 的所有变体返回,除非发生错误,该错误由 -1 发出信号。附加到字符串然后或多或少像:

size_t n = 0;

n += sprintf(str + n, ...);
n += sprintf(str + n, ...);

如果您使用 snprintf 而不是 sprintf,您还可以防止缓冲区溢出。

这有点麻烦。 s*printf 函数的问题是它们总是从头开始填充字符串并在后续调用同一输出缓冲区时覆盖数据,这有点不直观,因为 fprintf appnds 到文件并且不会覆盖之前写入同一输出文件的任何内容。

如果您有更多的情况需要通过连续调用格式化打印例程来构建字符串,您可以编写一个小框架。下面的示例从固定大小的 char 缓冲区创建一个 "appender" 并连续填充它。结果可能会被截断以防止溢出,但总是会产生一个以 null 结尾的字符串,除非 rem 是 0:

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>      // needed for va_list

struct appender {
    char *str;           // original buffer
    size_t rem;          // remaining space
    size_t n;            // (potential) characters written
};

int appprintf(struct appender *app, const char *fmt, ...)
{
    va_list args;
    char *p = app->rem ? app->str + app->n : NULL;
    int n;

    va_start(args, fmt);
    n = vsnprintf(p, app->rem, fmt, args);
    app->rem = (n < app->rem) ? app->rem - n : 0;
    app->n += n;
    va_end(args);

    return n;
}

int main()
{
    char buffer[64];
    struct appender app = { buffer, sizeof(buffer) };
    int i;

    for (i = 0; i < 100; i++) {
        appprintf(&app, " %d", i);
    }

    puts(buffer);

    return 0;
}