【问题标题】:Most carefull way to concatenate strings in C在 C 中连接字符串的最谨慎方法
【发布时间】:2015-04-30 11:07:45
【问题描述】:

我的固件每 10 秒一个周期将字符串移动到输出(SD 卡上的 csv 文件)。问题是字符串有时以不确定的方式改变添加不应该存在的值或在中间放置空间。这与 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

【问题讨论】:

  • 可能是输出太长,放不下分配的缓冲区?
  • 可能不会,因为它是一个周期600字节的信息。首先,我认为这是 RTC 的问题,但是将其更改为常量值并没有改变任何东西。
  • sprintf(FilStringMeas, "%s%f,", FilStringMeas, archDataVal[i]); -- 我不建议将输出缓冲区作为输入参数传递给 sprintf。一个 C 学究可能会纠正我,但我怀疑如果你这样做,你会得到不确定的结果,这可能是你遇到的麻烦的原因。 sprintf 进入一个临时缓冲区,或者更好的是,使用 snprintf 并明确缓冲区大小。
  • @JackWhitham 你是完全正确的,[v]s[n]printf() 不能提供它自己的输出缓冲区作为输入;在这种情况下,该行为被明确标记为未定义。
  • *printf 函数返回已解析格式字符串的长度,因此可以通过 n += sprintf(str + n, "..."); 之类的方法将更多格式化数据附加到字符串中。长度信息也可以用来防止缓冲区溢出。

标签: c string csv printf sd-card


【解决方案1】:

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

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

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

size_t n = 0;

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

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

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

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

#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;
}

【讨论】:

    【解决方案2】:

    我可以看到你使用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);
        }
    

    我会测试缓冲区大小。

    【讨论】:

    • 复制几乎和读取一样“便宜”,因此基于 strcat 的解决方案(有效)可能不会更有效。为了速度,您必须手动跟踪和推进当前写入位置。
    • 感谢您的帮助。我在代码中对其进行了更改,但它并没有解决我不需要的空格和值的问题。
    • @user3125367 :你是对的,我什至想过这个,但我懒得实现它。但在实际用例中,肯定是应该这样做的。
    猜你喜欢
    • 2017-01-26
    • 1970-01-01
    • 1970-01-01
    • 2016-12-11
    • 1970-01-01
    • 2015-04-30
    • 1970-01-01
    • 2015-12-18
    • 2010-09-06
    相关资源
    最近更新 更多