Бродячие символы, видимые при выводе snprintf

У меня есть функция создания строк в 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 Я исправил это с помощью snprintf а также strncat,

Примечание. Это встроенное приложение, и эта ошибка возникает один или два раза в день (из 1440 пакетов).

Вопрос
1. Может ли это быть вызвано прерыванием во время процесса snprintf/strncat?
2. Может ли это быть вызвано утечкой памяти, перезаписью стека или какой-либо другой проблемой сегментации, вызванной еще где?
В основном я хотел бы понять, что может быть причиной повреждения строки.

Трудно найти причину и исправить эту ошибку.


РЕДАКТИРОВАТЬ:
я использовал chux's функция. Ниже приведен минимальный, полный и проверяемый пример

/*
 * Test code for SO question https://stackru.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);
}

2 ответа

Решение

Вот (часть) моего беззастенчивого руководства по безопасной обработке строк в C. Обычно я бы рекомендовал динамическое выделение памяти вместо строк фиксированной длины, но в этом случае я предполагаю, что во встроенной среде это может быть проблематично. (Хотя такие предположения всегда следует проверять.)

Итак, обо всем по порядку:

  1. Любая функция, которая создает строку в буфере, должна быть явно указана, какова длина буфера. Это не подлежит обсуждению.

    Как должно быть очевидно, функция, заполняющая буфер, не может проверить переполнение буфера, если не знает, где заканчивается буфер. "Надеюсь, что буфер достаточно длинный" не является жизнеспособной стратегией. "Документировать необходимую длину буфера" было бы хорошо, если бы все внимательно прочитали документацию (они этого не делают) и если требуемая длина никогда не изменится (она будет). Единственное, что осталось, это дополнительный аргумент, который должен иметь тип size_t (потому что это тип длины буфера в функциях библиотеки C, которые требуют длины).

  2. Забудь про это strncpy а также strncat существовать. Также забудьте о strcat , Они не твои друзья.

    strncpy разработан для конкретного случая использования: обеспечение инициализации всего буфера фиксированной длины. Он не предназначен для обычных строк, и поскольку он не гарантирует, что вывод завершается NUL, он не создает строку.

    Если вы в любом случае собираетесь завершить NUL, вы также можете использовать memmove, или же memcpy если вы знаете, что источник и пункт назначения не перекрываются, что почти всегда должно иметь место. Так как вы захотите 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 выполнить поиск, насколько вы уверены, что ваша ранее вычисленная начальная точка верна?

    В приведенном выше фрагменте кода мы уже вычислили длину копии. Мы можем расширить это, чтобы сделать добавление без повторного сканирования:

    /* 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;
    }
    

    Может быть, мы хотим, чтобы исходные значения dst а также dstlen после создания строки, и это также может быть, что мы хотим знать, сколько байтов мы вставили в dst в целом. В этом случае мы, вероятно, захотим сделать копии этих переменных перед копированием и сохранить накопленную сумму ходов.

    Выше предполагается, что мы начинаем с пустого буфера вывода, но, возможно, это не так. Поскольку нам все еще нужно знать, где будет начинаться копия, чтобы узнать, сколько символов мы можем поставить в конце, мы все равно можем использовать memcpy; нам просто нужно сначала просканировать выходной буфер, чтобы найти точку копирования. (Делайте это только в том случае, если альтернативы нет. Делать это в цикле, а не записывать следующую точку копирования - это алгоритм Шлемиэля Художника.)

    /* 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;
    }
    
  3. обниматься 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 является то, что NUL, который заканчивается str именно в str + len, первый байт выходного буфера. Это совпадение, поэтому это незаконно.

    Второе важное замечание о snprintf является то, что он возвращает значение, которое нельзя игнорировать. Возвращаемое значение не является длиной строки, созданной snprintf, Это длина строки, если бы она не была усечена для размещения в буфере вывода.

    Если усечения не произошло, то результатом является длина результата, которая должна быть строго меньше размера выходного буфера (потому что должно быть место для терминатора NUL, который не считается частью длины результата.) Вы можете использовать этот факт, чтобы проверить, произошло ли усечение:

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

    Это может быть использовано, например, для повторного snprintf с большим динамически распределяемым буфером (размером result + 1; никогда не забывайте о необходимости NUL-терминации).

    Но помните, что результатом является int - то есть подписанное значение. Это означает, что snprintf не может справиться с очень длинными строками. Это вряд ли проблема во встроенном коде, но в системах, где возможно, что строки превышают 2 ГБ, вы не сможете безопасно использовать %s форматирует в snprintf, Это также означает, что snprintf разрешено возвращать отрицательное значение для обозначения ошибки. Очень старые реализации snprintf возвращается -1, чтобы указать усечение, или в ответ на вызов с длиной буфера 0. Это не стандартное поведение в соответствии с C99 (или недавними версиями Posix), но вы должны быть готовы к этому.

    Стандартные реализации snprintf вернет отрицательное значение, если аргумент длины буфера слишком велик, чтобы поместиться в (подписанный) int; мне не ясно, каково ожидаемое возвращаемое значение, если длина буфера в порядке, но не усеченная длина слишком велика для 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);
}

Заметки

  1. Вы часто будете видеть такой код:

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

    Рассказывая strncat не добавлять больше символов из src чем может вписаться src очевидно бессмысленно (если только src не корректно завершается NUL, в этом случае у вас есть большая проблема). Что еще более важно, он абсолютно ничего не защищает от записи за пределами выходного буфера, так как вы ничего не сделали для проверки того, что dst есть место для всех этих персонажей. Так что все, что он делает, это избавляется от предупреждений компилятора о небезопасности strcat, Поскольку этот код так же небезопасен, как strcat было, вам, вероятно, будет лучше с предупреждением.

  2. Вы можете даже найти компилятор, который понимает snprintf будет достаточно, чтобы проанализировать форматную строку во время компиляции, так что удобство приходит совершенно бесплатно. (И если ваш текущий компилятор не сделает этого, без сомнения, будущая версия будет.) Как и при любом использовании *printf семью, вы никогда не должны пытаться экономить нажатия клавиш, пропуская строку формата (snprintf(dst, dstlen, src) вместо snprintf(dst, dstlen, "%s", src).) Это небезопасно (имеет неопределенное поведение, если src содержит дубликат %). И это намного медленнее, потому что библиотечная функция должна анализировать всю строку, которая будет скопирована, в поисках знаков процента, а не просто копировать ее в вывод.

Код использует функции, которые ожидают указатели на строку, но не всегда передают указатели на строки в качестве аргументов.

Бродячие символы, видимые при выводе snprintf

Строка должна иметь завершающий нулевой символ.

strncat(char *, .... ожидает, что первый параметр будет указателем на строку. memcpy(output, "[",1); не страхует это. @Jeremy

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

Это потенциальный источник случайных персонажей.


strncat(...., ..., size_t size). сама проблема как size количество места, доступного для объединения (минус нулевой символ). Размер доступный для char * output не передается внутрь. @ Джонатан Леффлер. С таким же успехом 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
}

ОП выложил больше кода - рассмотрим.

Видимо буфер char *output предварительно заполнен 0 до get_gps60secString() поэтому отсутствующий нулевой символ из memcpy(output, "[",1); не должно вызывать проблему - хмммммм

unsigned short SendTrack() не возвращает значение. 1) Используя его значение результата UB. 2) Включить все предупреждения компилятора.

Другие вопросы по тегам