在 snprintf 的输出中看到的杂散字符

Posted

技术标签:

【中文标题】在 snprintf 的输出中看到的杂散字符【英文标题】:Stray characters seen at output of snprintf 【发布时间】:2018-09-04 10:26:02 【问题描述】:

我在 C 中有一个字符串创建函数,它接受 array of structs 作为它的参数,并根据预定义的格式输出一个字符串(如 python 中的列表列表)。 这是函数

typedef struct

    PacketInfo_t PacketInfo;
    char Gnss60[1900]; 
    //and other stuff...
 Track_json_t;

typedef struct 

    double latitude;
    double longitude;
 GPSPoint_t;

typedef struct

    UInt16          GPS_StatusCode;
    UInt32          fixtime;
    GPSPoint_t      point;
    double          altitude;
    unsigned char GPS_Satilite_Num;
 GPS_periodic_t;

unsigned short SendTrack()

    Track_json_t i_sTrack_S;
    memset(&i_sTrack_S, 0x00, sizeof(Track_json_t));
    getEvent_Track(&i_sTrack_S);
    //Many other stuff added to the i_sTrack_S struct...
    //Make a JSON format out of it
    BuildTrackPacket_json(&i_sTrack_S, XPORT_MODE_GPRS);


Track_json_t *getEvent_Track(Track_json_t *trk)

    GPS_periodic_t l_gps_60Sec[60];
    memset(&l_gps_60Sec, 0x00,
           sizeof(GPS_periodic_t) * GPS_PERIODIC_ARRAY_SIZE);
    getLastMinGPSdata(l_gps_60Sec, o_gps_base);
    get_gps60secString(l_gps_60Sec, trk->Gnss60);
    return trk;


void get_gps60secString(GPS_periodic_t input[60], char *output)

    int i = 0;
    memcpy(output, "[", 1); ///< Copy the first char as [
    char temp[31];
    for (i = 0; i < 59; i++)  //Run for n-1 elements
        memset(temp, 0, sizeof(temp));
        snprintf(temp, sizeof(temp), "[%0.8f,%0.8f],",
            input[i].point.latitude, input[i].point.longitude);
        strncat(output, temp, sizeof(temp));
    
    memset(temp, 0, sizeof(temp)); //assign last element
    snprintf(temp, sizeof(temp), "[%0.8f,%0.8f]]",
             input[i].point.latitude, input[i].point.longitude);
    strncat(output, temp, sizeof(temp));

所以函数的输出必须是格式的字符串

[[12.12345678,12.12345678],[12.12345678,12.12345678],...]

但有时我会得到一个看起来像

的字符串

[[12.12345678,12.12345678],[55.01[12.12345678,12.12345678],...] [[21.28211567,84.13454083],[21.28211533,21.22[21.28211517,84.13454000],..]

以前,我在函数 get_gps60secString 处出现缓冲区溢出,我通过使用 snprintfstrncat 解决了这个问题。

注意:这是一个嵌入式应用程序,此错误每天发生一到两次(共 1440 个数据包)

问题 1. 会不会是snprintf/strncat进程中断导致的? 2. 这可能是由内存泄漏、覆盖堆栈或其他地方引起的其他分段问题引起的吗? 基本上我想了解可能导致字符串损坏的原因。

很难找到原因并修复此错误。


编辑: 我使用了chux's 函数。以下是最小、完整且可验证的示例

/*
 * Test code for SO question https://***.com/questions/5216413
 * A Minimal, Complete, and Verifiable Example
 */

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <stdbool.h>
#include <signal.h>
#include <unistd.h>

typedef unsigned short UInt16;
typedef unsigned long  UInt32;

#define GPS_PERIODIC_ARRAY_SIZE  60
#define GPS_STRING_SIZE          1900

/* ---------------------- Data Structs --------------------------*/
typedef struct

    char Gnss60[GPS_STRING_SIZE];
 Track_json_t;

typedef struct

    double          latitude;
    double          longitude;
 GPSPoint_t;

typedef struct

    UInt16          GPS_StatusCode;
    UInt32          fixtime;
    GPSPoint_t      point;
    double          altitude;
    unsigned char GPS_Satilite_Num;
 GPS_periodic_t;

/* ----------------------- Global --------------------------------*/
FILE *fptr; //Global file pointer
int res = 0;
int g_last = 0;
GPS_periodic_t l_gps_60Sec[GPS_PERIODIC_ARRAY_SIZE];

/* ----------------------- Function defs --------------------------*/

/* At signal interrupt this function is called.
 * Flush and close the file. And safly exit the program */
void userSignalInterrupt()

    fflush(fptr);
    fclose(fptr);
    res = 1;
    exit(0);


/* @brief From the array of GPS structs we create a string of the format
 * [[lat,long],[lat,long],..]
 * @param   input   The input array of GPS structs
 * @param   output  The output string which will contain lat, long
 * @param   sz      Size left in the output buffer
 * @return  0       Successfully completed operation
 *          1       Failed / Error
 */
int get_gps60secString(GPS_periodic_t input[GPS_PERIODIC_ARRAY_SIZE], 
                       char *output, size_t sz) 

    int cnt = snprintf(output, sz, "[");
    if (cnt < 0 || cnt >= sz)
        return 1;
    output += cnt;
    sz -= cnt;

    int i = 0;
    for (i = 0; i < GPS_PERIODIC_ARRAY_SIZE; i++) 
        cnt = snprintf(output, sz, "[%0.8f,%0.8f]%s", 
                input[i].point.latitude, input[i].point.longitude, 
                i + 1 == GPS_PERIODIC_ARRAY_SIZE ? "" : ",");
        if (cnt < 0 || cnt >= sz)
            return 1;
        output += cnt;
        sz -= cnt;
    

    cnt = snprintf(output, sz, "]");
    if (cnt < 0 || cnt >= sz)
        return 1;
    return 0; // no error


/* @brief   Create a GPS struct with data for testing. It will populate the
 * point field of GPS_periodic_t. Lat starts from 0.0 and increases by 1*10^(-8)
 * and Long will dstart at 99.99999999 and dec by 1*10^(-8)
 *
 * @param   o_gps_60sec Output array of GPS structs
 */
void getLastMinGPSdata(GPS_periodic_t *o_gps_60sec)

    //Fill in GPS related data here
    int i = 0;
    double latitude = o_gps_60sec[0].point.latitude;
    double longitude = o_gps_60sec[0].point.longitude;
    for (i = 0; i < 60; i++)
    
        o_gps_60sec[i].point.latitude = latitude +  (0.00000001 * (float)g_last + 
                                        0.00000001 * (float)i);
        o_gps_60sec[i].point.longitude = longitude -  (0.00000001 * (float)g_last + 
                                        0.00000001 * (float)i);
    
    g_last = 60;


/* @brief   Get the GPS data and convert it into a string
 * @param   trk Track structure with GPS string
 */
int getEvent_Track(Track_json_t *trk)

    getLastMinGPSdata(l_gps_60Sec);
    get_gps60secString(l_gps_60Sec, trk->Gnss60, GPS_STRING_SIZE);

    return 0;


int main()

    fptr = fopen("gpsAno.txt", "a");
    if (fptr == NULL) 
        printf("Error!!\n");
        exit(1);
    

    //Quit at signal interrupt
    signal(SIGINT, userSignalInterrupt);

    Track_json_t trk;
    memset(&l_gps_60Sec, 0x00, sizeof(GPS_periodic_t) * GPS_PERIODIC_ARRAY_SIZE);

    //Init Points to be zero and 99.99999999
    int i = 0;
    for (i = 0; i < 60; i++) 
        l_gps_60Sec[i].point.latitude =  00.00000000;
        l_gps_60Sec[i].point.longitude = 99.99999999;
    

    do 
        memset(&trk, 0, sizeof(Track_json_t));
        getEvent_Track(&trk);

        //Write to file
        fprintf(fptr, "%s", trk.Gnss60);
        fflush(fptr);
        sleep(1);
     while (res == 0);

    //close and exit
    fclose(fptr);
    return  0;

注意:上述代码中没有重新创建错误。 因为这没有strcat 的陷阱。 我在嵌入式应用程序中测试了这个功能。 通过这个我能够发现snprintf返回一个错误并且创建的字符串最终是:

[17.42401750,78.46098717],[17.42402083,53.62

它在那里结束(因为return 1)。

这是否意味着传递给snprints 的数据已损坏?这是一个浮点值。它怎么会损坏?

解决方案 自从我将 sprintf 函数更改为不直接处理 64 位数据的函数后,该错误尚未出现。

这里是函数modp_dtoa2

/** \brief convert a floating point number to char buffer with a
 *         variable-precision format, and no trailing zeros
 *
 * This is similar to "%.[0-9]f" in the printf style, except it will
 * NOT include trailing zeros after the decimal point.  This type
 * of format oddly does not exists with printf.
 *
 * If the input value is greater than 1<<31, then the output format
 * will be switched exponential format.
 *
 * \param[in] value
 * \param[out] buf  The allocated output buffer.  Should be 32 chars or more.
 * \param[in] precision  Number of digits to the right of the decimal point.
 *    Can only be 0-9.
 */
void modp_dtoa2(double value, char* str, int prec)

    /* if input is larger than thres_max, revert to exponential */
    const double thres_max = (double)(0x7FFFFFFF);
    int count;
    double diff = 0.0;
    char* wstr = str;
    int neg= 0;
    int whole;
    double tmp;
    uint32_t frac;

    /* Hacky test for NaN
     * under -fast-math this won't work, but then you also won't
     * have correct nan values anyways.  The alternative is
     * to link with libmath (bad) or hack IEEE double bits (bad)
     */
    if (! (value == value)) 
        str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\0';
        return;
    

    if (prec < 0) 
        prec = 0;
     else if (prec > 9) 
        /* precision of >= 10 can lead to overflow errors */
        prec = 9;
    

    /* we'll work in positive values and deal with the
       negative sign issue later */
    if (value < 0) 
        neg = 1;
        value = -value;
    


    whole = (int) value;
    tmp = (value - whole) * pow10[prec];
    frac = (uint32_t)(tmp);
    diff = tmp - frac;

    if (diff > 0.5) 
        ++frac;
        /* handle rollover, e.g.  case 0.99 with prec 1 is 1.0  */
        if (frac >= pow10[prec]) 
            frac = 0;
            ++whole;
        
     else if (diff == 0.5 && ((frac == 0) || (frac & 1))) 
        /* if halfway, round up if odd, OR
           if last digit is 0.  That last part is strange */
        ++frac;
    

    /* for very large numbers switch back to native sprintf for exponentials.
       anyone want to write code to replace this? */
    /*
      normal printf behavior is to print EVERY whole number digit
      which can be 100s of characters overflowing your buffers == bad
    */
    if (value > thres_max) 
        sprintf(str, "%e", neg ? -value : value);
        return;
    

    if (prec == 0) 
        diff = value - whole;
        if (diff > 0.5) 
            /* greater than 0.5, round up, e.g. 1.6 -> 2 */
            ++whole;
         else if (diff == 0.5 && (whole & 1)) 
            /* exactly 0.5 and ODD, then round up */
            /* 1.5 -> 2, but 2.5 -> 2 */
            ++whole;
        

        //vvvvvvvvvvvvvvvvvvv  Diff from modp_dto2
     else if (frac) 
        count = prec;
        // now do fractional part, as an unsigned number
        // we know it is not 0 but we can have leading zeros, these
        // should be removed
        while (!(frac % 10)) 
            --count;
            frac /= 10;
        
        //^^^^^^^^^^^^^^^^^^^  Diff from modp_dto2

        // now do fractional part, as an unsigned number
        do 
            --count;
            *wstr++ = (char)(48 + (frac % 10));
         while (frac /= 10);
        // add extra 0s
        while (count-- > 0) *wstr++ = '0';
        // add decimal
        *wstr++ = '.';
    

    // do whole part
    // Take care of sign
    // Conversion. Number is reversed.
    do *wstr++ = (char)(48 + (whole % 10)); while (whole /= 10);
    if (neg) 
        *wstr++ = '-';
    
    *wstr='\0';
    strreverse(str, wstr-1);

【问题讨论】:

您作为output 传递的字符串最初会被零初始化吗?否则当你调用strncat时它不会被终止(它必须是)。 output指向的缓冲区够大吗?如果您创建了一个Minimal, Complete, and Verifiable Example 来向我们展示,会更容易为您提供帮助。 您误用 strncat()strncat(output, temp, sizeof(temp)); — 大小应该是目标中剩余的空间量减去 1 的空字节(所以最多 sizeof(output) - 1 如果 output 是已知大小的数组)。鉴于您的函数不知道 output 数组有多大,您无法防止该数组中的溢出,并且使用 strncat() 是个坏主意。注意char buffer[16] = ""; strncat(buffer, "abcdefghijklmnop", sizeof(buffer)); 会导致缓冲区溢出! 您仍然缺少一个非常重要的部分:Track_json_t 类型,更具体地说是它的 Gnss60 成员。它是数组还是指针?如果它是一个指针,它指向哪里?从您显示的代码来看,由于memset 调用,它相当于一个空指针(如果它是一个指针)。 鉴于您正在祈祷并希望,请使用strcat()。正确使用strncat() 很难;大多数人都弄错了。使用它是希望当必要的信息不可用时它会保护你(你需要output 的大小作为函数的参数)可能会让你陷入一种虚假的安全感。就个人而言,我从不使用strncat()——如果我知道足够安全地使用它,还有更好的选择,例如memmove()memcpy()。 (知足就意味着output有多长,使用了多少,即将添加多少。) 【参考方案1】:

这是我毫不掩饰地固执己见的 C 中安全字符串处理指南的(部分)。通常,我会提倡动态内存分配而不是固定长度的字符串,但在这种情况下,我假设在嵌入式环境中可能是有问题的。 (尽管应始终检查此类假设。)

所以,第一件事:

    任何在缓冲区中创建字符串的函数都必须明确告知缓冲区的长度。这是不可协商的。

    应该很明显,填充缓冲区的函数不可能检查缓冲区溢出,除非它知道缓冲区的结束位置。 “希望缓冲区足够长”不是一个可行的策略。如果每个人都仔细阅读文档(他们没有)并且如果所需的长度永远不会改变(它会),“记录所需的缓冲区长度”会很好。唯一剩下的是一个额外的参数,它应该是 size_t 类型(因为这是 C 库函数中需要长度的缓冲区长度类型)。

    忘记strncpystrncat 的存在。也忘了strcat。他们不是你的朋友。

    strncpy 是为特定用例设计的:确保初始化整个固定长度的缓冲区。它不是为普通字符串设计的,因为它不能保证输出是 NUL 终止的,所以它不会产生字符串。

    如果您要自己终止 NUL,则最好使用 memmovememcpy,如果您知道源和目标不重叠(几乎总是如此)。由于您希望 memmove 停止在短字符串的字符串末尾(strncpy 确实 这样做),所以首先使用 strnlen 测量字符串长度:strnlen需要一个最大长度,这正是您要移动最大字符数的情况下想要的。

    示例代码:

    /* Safely copy src to dst where dst has capacity dstlen. */
    if (dstlen) 
      /* Adjust to_move will have maximum value dstlen - 1 */
      size_t to_move = strnlen(src, dstlen - 1);
      /* copy the characters */
      memmove(dst, src, to_move);
      /* NUL-terminate the string */
      dst[to_move] = 0;
    
    

    strncat 的语义稍微合理一些,但它实际上从来没有用过,因为要使用它,您必须知道可以复制多少字节。为了知道这一点,在实践中,您需要知道输出缓冲区中还剩下多少空间,并且需要知道复制将从输出缓冲区的哪个位置开始。 [注 1]。但是,如果您已经知道复制将从哪里开始,那么从头开始搜索缓冲区以找到复制点的意义何在?如果您让strncat 进行搜索,您有多确定您之前计算的起点是正确的?

    在上面的代码 sn-p 中,我们已经计算了副本的长度。我们可以扩展它来进行追加而不需要重新扫描:

    /* Safely copy src1 and then src2 to dst where dst has capacity dstlen. */
    /* Assumes that src1 and src2 are not contained in dst. */
    if (dstlen) 
      /* Adjust to_move will have maximum value dstlen - 1 */
      size_t to_move = strnlen(src1, dstlen - 1);
      /* Copy the characters from src1 */
      memcpy(dst, src1, to_move);
      /* Adjust the output pointer and length */
      dst += to_move;
      dstlen -= to_move;
      /* Now safely copy src2 to just after src1. */
      to_move = strnlen(src2, dstlen - 1);
      memcpy(dst, src2, to_move);
      /* NUL-terminate the string */
      dst[to_move] = 0;
    
    

    可能是我们想知道创建字符串后dstdstlen 的原始值,也可能是我们想知道我们在dst 中插入了多少字节。在这种情况下,我们可能希望在复制之前复制这些变量,并保存移动的累积总和。

    上面假设我们从一个空的输出缓冲区开始,但也许不是这样。因为我们仍然需要知道副本从哪里开始才能知道最后可以放多少个字符,所以我们仍然可以使用memcpy;我们只需要先扫描输出缓冲区以找到复制点。 (只有在别无选择的情况下才这样做。循环执行而不是记录下一个复制点是Shlemiel the Painter's algorithm。)

    /* Safely append src to dst where dst has capacity dstlen and starts
     * with a string of unknown length.
     */
    if (dstlen) 
      /* The following code will "work" even if the existing string
       * is not correctly NUL-terminated; the code will not copy anything
       * from src, but it will put a NUL terminator at the end of the
       * output buffer.
       */
      /* Figure out where the existing string ends. */
      size_t prefixlen = strnlen(dst, dstlen - 1);
      /* Update dst and dstlen */
      dst += prefixlen;
      dstlen -= prefixlen;
      /* Proceed with the append, as above. */
      size_t to_move = strnlen(src, dstlen - 1);
      memmove(dst, src, to_move);
      dst[to_move] = 0;
    
    

    拥抱snprintf。它真的是你的朋友。但始终检查它的返回值。

    如上所述,使用memmove 有点尴尬。它需要您手动检查缓冲区的长度是否不为零(否则减一将是灾难性的,因为长度是无符号的),并且需要您手动 NUL 终止输出缓冲区,这很容易忘记,也是许多来源错误。它非常高效,但有时牺牲一点效率是值得的,以便您的代码更易于编写、更易于阅读和验证。

    这直接将我们引向snprintf。例如,您可以替换:

    if (dstlen) 
      size_t to_move = strnlen(src, dstlen - 1);
      memcpy(dst, src, to_move);
      dst[to_move] = 0;
    
    

    用更简单的

    int copylen = snprintf(dst, dstlen, "%s", src);
    

    什么都做:检查dstlen 不为0;只复制来自src 的字符,这些字符可以适合dst,并且正确地以NUL 终止dst(除非dstlen 为0)。而且成本最低;解析格式字符串"%s" 只需要很少的时间,并且大多数实现都针对这种情况进行了很好的优化。 [注2]

    snprintf 不是万能药。还有几个非常重要的警告。

    首先,snprintf 的文档明确表示不允许任何输入参数与输出范围重叠。 (所以它替换了memcpy 但不是memmove。)请记住,重叠包括NUL 终止符,所以下面的代码尝试将str 中的字符串加倍而不是导致未定义的行为

    char str[BUFLEN];
    /* Put something into str */
    get_some_data(str, BUFLEN);
    
    /* DO NOT DO THIS: input overlaps output */
    int result = snprintf(str, BUFLEN, "%s%s", str, str);
    
    /* DO NOT DO THIS EITHER; IT IS STILL UB */
    size_t len = strnlen(str, cap - 1);
    int result = snprintf(str + len, cap - len, "%s", str);    
    

    第二次调用snprintf 的问题在于终止str 的NUL 恰好位于str + len,即输出缓冲区的第一个字节。这是重叠的,所以是非法的。

    关于snprintf 的第二个重要注意事项是它返回一个值,不能忽略该值。返回的值不是snprintf 创建的字符串的长度。如果字符串没有被截断以适合输出缓冲区,它就是该字符串的长度。

    如果没有发生截断,那么结果就是结果的长度,它必须严格小于输出缓冲区的大小(因为必须为 NUL 终止符留出空间,即不考虑结果长度的一部分。)您可以使用这个事实来检查是否发生截断:

    if (result >= dstlen) /* Output was truncated */
    

    这可用于,例如,使用更大的动态分配缓冲区(大小为 result + 1;永远不要忘记 NUL 终止的需要)重做 snprintf

    但请记住,结果是int——即有符号值。这意味着snprintf 无法处理很长的字符串。这在嵌入式代码中不太可能成为问题,但在可以想象字符串超过 2GB 的系统上,您可能无法安全地使用snprintf 中的%s 格式。这也意味着snprintf 被允许返回一个负值来表示错误。 snprintf 的非常旧的实现返回 -1 以指示截断,或者响应以缓冲区长度 0 调用。根据 C99(也不是 Posix 的最新版本),这不是标准行为,但您应该为此做好准备。

    如果缓冲区长度参数太大而无法放入(有符号的)int,则snprintf 的标准兼容实现将返回负值;如果缓冲区长度正常但未截断的长度对于int 来说太大,我不清楚预期的返回值是多少。如果您使用了导致编码错误的转换,也会返回负值;例如,%lc 转换,其对应参数包含无法转换为多字节(通常为 UTF-8)序列的整数。

    简而言之,你应该经常检查snprintf的返回值(如果你不这样做,最近的gcc/glibc版本会产生一个警告),你应该准备它是否定的。


所以,在我们完成所有这些之后,让我们编写一个函数来生成一串坐标对:

/* Arguments:
 *    buf      the output buffer.
 *    buflen   the capacity of buf (including room for trailing NUL).
 *    points   a vector of struct Point pairs.
 *    npoints  the number of objects in points.
 * Description:
 *    buf is overwritten with a comma-separated list of points enclosed in
 *    square brackets. Each point is output as a comma-separated pair of
 *    decimal floating point numbers enclosed in square brackets. No more
 *    than buflen - 1 characters are written. Unless buflen is 0, a NUL is
 *    written following the (possibly-truncated) output.
 * Return value:
 *    If the output buffer contains the full output, the number of characters
 *    written to the output buffer, not including the NUL terminator.
 *    If the output was truncated, (size_t)(-1) is returned.
 */
 size_t sprint_points(char* buf, size_t buflen,
                      struct Point const* points, size_t npoints)
  
   if (buflen == 0) return (size_t)(-1);
   size_t avail = buflen;
   char delim = '['
   while (npoints) 
     int res = snprintf(buf, avail, "%c[%f,%f]",
                        delim, points->lat, points->lon);
     if (res < 0 || res >= avail) return (size_t)(-1);
     buf += res; avail -= res;
     ++points; --npoints;
     delim = ',';
  
  if (avail <= 1) return (size_t)(-1);
  strcpy(buf, "]");
  return buflen - (avail - 1);

注意事项

    你会经常看到这样的代码:

    strncat(dst, src, sizeof(src)); /* NEVER EVER DO THIS! */
    

    告诉strncat 不要在src 中添加比src 更多的字符显然是没有意义的(除非src 没有正确地以NUL 终止,在这种情况下你会遇到更大的问题)。更重要的是,它绝对没有来保护您不写超出输出缓冲区的末尾,因为您没有做任何事情来检查dst 是否有空间容纳所有这些字符。所以它所做的一切就是摆脱关于strcat不安全的编译器警告。由于此代码strcat 一样不安全,因此您可能最好接受警告。

    您甚至可能会发现一个能够理解snprintf 的编译器足以在编译时解析格式字符串,因此完全无需任何成本。 (如果您当前的编译器不这样做,那么毫无疑问,未来的版本会这样做。)与 *printf 系列的任何使用一样,您应该永远尝试通过以下方式来节省击键 省略格式字符串(snprintf(dst, dstlen, src) 而不是snprintf(dst, dstlen, "%s", src)。)这是不安全(如果src 包含不重复的%,它具有未定义的行为)。而且它要慢得多,因为库函数必须解析要复制的整个字符串以查找百分号,而不是仅仅将其复制到输出中。

【讨论】:

我还没有校对任何代码;我去吃午饭了。如果您发现错误,请告诉我,我会在回来后愉快地修复它。 我正在通过double 点,但在我的 sprintf 中,我期待 %f 浮动。根据***.com/questions/25860850/… 为printf 家庭%f%lf 是相同的。我的 arm-gcc 版本是 C99。这里会有问题吗? 看起来从 C99 及更高版本开始,printf 家族接受双精度(%f%lf)***.com/a/25860890/5698740 @clmno:%f printf 格式打印double。它一直这样做,而且永远都会这样做。 C99 慷慨地允许多余的l 标志,但它没有任何改变。直接打印float 是不可能的,因为像printf 这样的可变参数函数不能接受float 参数。他们也不能接受shortchar 参数。当然,您可以使用这样的值调用该函数,但 floats 将转换为 double 并且比 int 窄的整数类型将转换为 int【参考方案2】:

代码使用的函数期望指向 string 的指针,但并不总是将指向 strings 的指针作为参数传递。

在 snprintf 的输出中看到的杂散字符

字符串必须有一个终止空字符

strncat(char *, .... 期望第一个参数是指向字符串的指针。 memcpy(output, "[",1); 不保证这一点。 @Jeremy

memcpy(output, "[",1);
...
strncat(output, temp,sizeof(temp));

这是杂散字符的候选来源。


strncat(...., ..., size_t size). 本身就是一个问题,因为size 是可用于连接的空间量(减去空字符)。 char * output 可用的大小未传入。@Jonathan Leffler。不妨在这里做strcat()

改为将可用大小传递给output 以防止缓冲区溢出。

#define N 60

int get_gps60secString(GPS_periodic_t input[N], char *output, size_t sz) 
  int cnt = snprintf(output, sz, "[");
  if (cnt < 0 || cnt >= sz)
    return 1;
  output += cnt;
  sz -= cnt;

  int i = 0;
  for (i = 0; i < N; i++) 
    cnt = snprintf(output, size, "[%0.8f,%0.8f]%s", input[i].point.latitude,
        input[i].point.longitude, i + 1 == N ? "" : ",");
    if (cnt < 0 || cnt >= sz)
      return 1;
    output += cnt;
    sz -= cnt;
  

  cnt = snprintf(output, sz, "]");
  if (cnt < 0 || cnt >= sz)
    return 1;
  return 0; // no error


OP 已发布更多代码 - 将审核。

显然char *output 缓冲区在get_gps60secString() 之前预填充了 0,因此memcpy(output, "[",1); 中缺少的 null 字符 应该不会导致问题 - hmmmmmm

unsigned short SendTrack() 不返回值。 1)使用它的结果值是UB。 2) 启用所有编译器警告。

【讨论】:

我没有包括那个。它确实有回报。我将制作一个可编译的代码并分享它。但请注意,我必须解决诸如 GPS 数据 (HW) 之类的问题 相关:What are the functions from the standard library that must/should be avoided? @Lundin 有趣的list。除了atoi(),我同意“永远不应该使用的功能”。然而,在我的书中“应谨慎使用的函数:”是标准库的其余部分。似乎每个函数在 SO 上都有误用的可能性。 @chux 我使用了你创建的“get_gps60secString”函数。但仍然出现错误。这是因为 snprintf 的数据 I/P 已损坏吗?请参阅问题的编辑部分。 @ClamentJohn 我不这么认为。我怀疑你的缓冲区太小了。需要查看get_gps60secString() 的调用方式及其参数的值。

以上是关于在 snprintf 的输出中看到的杂散字符的主要内容,如果未能解决你的问题,请参考以下文章

调试节点集群中的杂散未捕获异常 (ECONNRESET)

程序中的杂散“\303”和杂散“\215”——为啥?

telnet 协商中的杂散数据字节。它来自哪里?

string中的杂散知识点

测量电压调节器输出纹波和开关瞬变的方法

在vim中的空格后轻松对齐字符