Общее пространство памяти для локально определенных буферов?
В классе у меня есть метод, который использует базовый буфер символов для печати некоторых данных, что-то вроде
char buffer[64];
sprintf(buffer, "%s: some text: %u, %u, %u %u", string_var, u_var1, u_var2, u_var3, u_var4);
Serial.println(buffer);
Все переменные определяются локально для этого метода внутри класса.
На данный момент я закомментировал Serial.println(buffer), так как он является частью раздела отладки прекомпилятора. Я знаю, что этот буфер не печатается из этого места. Буфер все еще загружается данными, но НЕ печатается.
В моем основном коде есть еще одна функция, использующая аналогичный формат, но у буфера другое имя.
char json_buffer[256];
Serial.println(json_buffer);
опять же, все определено локально, НЕ глобально.
Теперь эта последовательная строка печати печатает содержимое первого символьного буфера[64], ЕСЛИ Я НЕ создаю его с нулевым завершением из выключенного состояния, например:
char json_buffer[256] ={"\n"};
Почему это происходит? это утечка памяти? переполнение буфера? Я пробовал увеличить размер каждого буфера, например, первый буфер, который я пробовал, с размером 512 символов, а также второй с 512 символами, но независимо от того, что я пытаюсь, содержимое первого буфера печатается, когда я печатаю второй один.
Просто ищу объяснение и надежный способ предотвратить это. Спасибо!
Полный код находится по адресу: https://github.com/practable/холодильник/tree/dev/fw/холодильная прошивка Но это довольно обширная программа
@ImogenWren, 👍-1
Обсуждение3 ответа
Автоматические переменные живут в стеке на протяжении всего блока, в котором они были созданы — возможно, на протяжении всей каждой из этих функций. Они не инициализируются автоматически, а просто считаются используемыми на время выполнения каждой функции.
Поэтому вероятный ответ заключается в том, что buffer
и json_buffer
перекрываются, по крайней мере, в некоторой степени. В этом случае, поскольку вы не инициализировали json_buffer, все, что из него будет напечатано, — это любые данные, которые ранее оставались в области памяти, которую они сейчас занимают. Мы не можем точно видеть ваш код, но поскольку этот сценарий может точно описать то, что вы видите, я бы сказал, что это, скорее всего, правильное объяснение.
Просто ищу объяснение и надежный способ предотвратить это.
Все очень просто: не используйте содержимое неинициализированных переменных; всегда инициализируйте их перед использованием.
Для тех, кто еще не знаком с условиями, условия могут быть не совсем понятны.
- "Для инициализации" обычно означает запись значения в переменную каким-либо способом.
- "Для использования" обычно означает чтение из переменной.
Языком программирования здесь является C++, который примерно работает следующим образом:
- Не
статическая
(динамическая) переменная, локальная для функции или метода, не инициализируется без явной инициализации или присвоения:/* ... */ { int not_initialized; int initialized = 23; int written_to; // здесь неинициализированный written_to = 42; // теперь "инициализировано" }
- Переменная
static
без явной инициализации инициализируется по умолчанию:
Это не зависит от области видимости переменной. Он может находиться внутри любой функции или метода или снаружи на уровне модуля.static int default_initialized; // = 0; по спецификации стандарта
- Глобальная переменная без явной инициализации инициализируется по умолчанию:
// вне любой функции или метода int default_initialized; // = 0; по спецификации стандарта
Имея это в виду, нет смысла инициализировать динамические переменные и записывать в них (что некоторые считают «использовать») без промежуточного использования (чтения). Некоторые компиляторы могут даже предупреждать об этом запахе кода:
/* ... */ {
int initialized = 0;
// другой код без доступа к "инициализированному"
initialized = 2024; // вторая запись
// Странно, почему бы не инициализировать его напрямую?
// Или программист забыл что-то между ними?
}
Если ваш компилятор хорошо оптимизируется, он может удалить инициализацию.
Я видел такое же поведение, когда инициализировал переменную json_buffer, вводя в нее некоторые данные, неожиданная строка из другого буфера печаталась раньше новых данных., @ImogenWren
@ImogenWren Пожалуйста, отредактируйте свой вопрос и предоставьте минимальный, полный и воспроизводимый пример проблемы. Я не буду просматривать ваш обширный источник на каком-то внешнем веб-сайте. Вы можете начать с удаления ненужных частей, пока не останется примерно 10–20 строк кода., @the busybee
Из вашего связанного кода мне кажется, что это недействительно:
sprintf(json_buffer, "%s%s", json_buffer, json_footer);
Похоже, вы пытаетесь добавить json_footer к json_buffer, однако sprintf начнет запись в начало json_buffer, отбрасывая то, что там уже есть. Я предлагаю вместо этого использовать strcat
.
Вы делаете это часто, и существует вероятность состояния гонки, когда в процессе записи в json_buffer таким недопустимым способом sprintf выходит из-под контроля и записывает гораздо больше, чем следует.
р>В качестве дополнения к ответу Ника Гаммона: если вы хотите построить
JSON, используя sprintf()
, вы должны отслеживать текущий
позиция конца строки (\0
, завершающий ее): это
где вы должны написать следующий бит, таким образом, это указатель, который вы будете
предоставить sprintf()
.
Кроме того, вместо этого вы можете использовать snprintf()
, просто чтобы убедиться, что вы
никогда не переполняйте буфер.
Вот пример построения строки с помощью нескольких вызовов
snprintf()
, отслеживая текущую позицию записи:
char *build_json() {
static char buffer[620];
char *end = buffer + sizeof buffer; // конец буфера
char *p = buffer; // текущая позиция записи
p += snprintf(p, end-p, "{\n");
p += snprintf(p, end-p, " \"timestamp\": %d,\n", 1704391201);
p += snprintf(p, end-p, " \"valves\": {\n");
p += snprintf(p, end-p, " \"V1\": %d,\n", rand() % 2);
p += snprintf(p, end-p, " \"V2\": %d\n", rand() % 2);
p += snprintf(p, end-p, " },\n");
p += snprintf(p, end-p, " \"relays\": {\n");
p += snprintf(p, end-p, " \"foo\": %d,\n", rand() % 2);
p += snprintf(p, end-p, " \"bar\": %d\n", rand() % 2);
p += snprintf(p, end-p, " }\n");
p += snprintf(p, end-p, "}\n");
return buffer;
}
- Как разделить входящую строку?
- Как вывести несколько переменных в строке?
- В чем разница между Serial.write и Serial.print? И когда они используются?
- Загрузка Arduino Nano дает ошибку: avrdude: stk500_recv(): programmer is not responding
- Программы построения последовательных данных
- Как узнать частоту дискретизации?
- Что такое Serial.begin(9600)?
- Очистить существующий массив при получении новой последовательной команды
Зачем вам печатать неинициализированный буфер?, @Edgar Bonet
Возможно, если вы опубликуете весь свой код, а не только несколько фрагментов., @Nick Gammon
@EdgarBonet Если я выполняю распечатку содержимого, которое хочу разместить в этом буфере, он также распечатывает содержимое первого буфера перед строкой, в которой я действительно хотел находиться там. Я исключил этот дополнительный шаг во время отладки и обнаружил, что неожиданное поведение все еще присутствовал., @ImogenWren
@NickGammon Весь код занимает 7 страниц, я опубликовал отрывок, который, по моему мнению, имеет отношение к этой проблеме. Полную программу можно найти по адресу https://github.com/practable/registration/tree/dev/fw/residentialFirmware. Я отредактирую вопрос, чтобы он содержал это., @ImogenWren
Если ваш код слишком длинный, опубликуйте вместо него [Минимальный воспроизводимый пример](https://stackoverflow.com/help/minimal-reproducible-example), показывающий проблему., @Edgar Bonet