Бродячие символы, видимые при выводе 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. Обычно я бы рекомендовал динамическое выделение памяти вместо строк фиксированной длины, но в этом случае я предполагаю, что во встроенной среде это может быть проблематично. (Хотя такие предположения всегда следует проверять.)
Итак, обо всем по порядку:
Любая функция, которая создает строку в буфере, должна быть явно указана, какова длина буфера. Это не подлежит обсуждению.
Как должно быть очевидно, функция, заполняющая буфер, не может проверить переполнение буфера, если не знает, где заканчивается буфер. "Надеюсь, что буфер достаточно длинный" не является жизнеспособной стратегией. "Документировать необходимую длину буфера" было бы хорошо, если бы все внимательно прочитали документацию (они этого не делают) и если требуемая длина никогда не изменится (она будет). Единственное, что осталось, это дополнительный аргумент, который должен иметь тип
size_t
(потому что это тип длины буфера в функциях библиотеки C, которые требуют длины).Забудь про это
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; }
обниматься
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);
}
Заметки
Вы часто будете видеть такой код:
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
содержит дубликат%
). И это намного медленнее, потому что библиотечная функция должна анализировать всю строку, которая будет скопирована, в поисках знаков процента, а не просто копировать ее в вывод.
Код использует функции, которые ожидают указатели на строку, но не всегда передают указатели на строки в качестве аргументов.
Бродячие символы, видимые при выводе 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) Включить все предупреждения компилятора.