Общее пространство памяти для локально определенных буферов?

В классе у меня есть метод, который использует базовый буфер символов для печати некоторых данных, что-то вроде

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/холодильная прошивка Но это довольно обширная программа

, 👍-1

Обсуждение

Зачем вам печатать неинициализированный буфер?, @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


3 ответа


0

Автоматические переменные живут в стеке на протяжении всего блока, в котором они были созданы — возможно, на протяжении всей каждой из этих функций. Они не инициализируются автоматически, а просто считаются используемыми на время выполнения каждой функции.

Поэтому вероятный ответ заключается в том, что 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


3

Из вашего связанного кода мне кажется, что это недействительно:

sprintf(json_buffer, "%s%s", json_buffer, json_footer);  

Похоже, вы пытаетесь добавить json_footer к json_buffer, однако sprintf начнет запись в начало json_buffer, отбрасывая то, что там уже есть. Я предлагаю вместо этого использовать strcat.

Вы делаете это часто, и существует вероятность состояния гонки, когда в процессе записи в json_buffer таким недопустимым способом sprintf выходит из-под контроля и записывает гораздо больше, чем следует.

р>
,

1

В качестве дополнения к ответу Ника Гаммона: если вы хотите построить 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;
}
,