Бродячие символы, видимые при выводе snprintf - PullRequest
0 голосов
/ 04 сентября 2018

У меня есть функция создания строки в 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. Может ли это быть вызвано утечкой памяти, перезаписью стека или какой-либо другой проблемой сегментации, вызванной где-то еще?
В основном я хотел бы понять, что может быть причиной повреждения строки.

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


EDIT:
Я использовал chux's функцию. Ниже приведен минимальный, полный и проверяемый пример

/*
 * Test code for SO question https://stackoverflow.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 ]

0 голосов
/ 04 сентября 2018

Вот (часть) моего беззастенчивого руководства по безопасной обработке строк в 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 содержит не дублированный %). И это намного медленнее , потому что библиотечная функция должна анализировать всю строку, которая будет скопирована, в поисках знаков процента, а не просто копировать ее в вывод.

0 голосов
/ 04 сентября 2018

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

При выводе snprintf

появляются случайные символы

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

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

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) Включить все предупреждения компилятора.

...