Вопрос с преобразованием в char

Добрый день,
Есть вот такой тестовый скетч. Читаю байт из памяти esp32, преобразовываю значение в char, добавляю его к массиву char и вывожу на экран.

byte address_max_temp = 0; 
byte value_max_temp; 

char system_text[] = "serial:111222\n"
"batch:9\n"
"max_t:";

char stFileName[] = "";

char ByteToChar(byte byteSt, byte num)
{
  const byte offset = 48;
  if (num==1) return char(offset+byteSt/100);
  if (num==2) return char(offset+byteSt/10-byteSt/100*10);
  if (num==3) return char(offset+byteSt-byteSt/10*10); 
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(5);
  value_max_temp = EEPROM.read(address_max_temp);
  byte i;  

  for (i = 0; i < 3; i++) {
    stFileName[i] = ByteToChar(value_max_temp, i+1);
  }
  strcat(system_text, stFileName);
  Serial.print("MAX TEMP=");
  Serial.println(stFileName);
  Serial.println(system_text);
}

void loop() {}

Если я объявляю stFileName в setup, то почему то в получаемом system_text добавляется помимо самого stFileName еще несколько симоволов, а если объявляю stFileName глобальной переменной (как в примере), то получаемый system_text корректный.
stFileName используется единожды и объявлять его глобальной переменной необходимости нет, но в этом случае работает некоректно. Просвятите пожалуйста.

Хэх, я бы наверно использовал юнион, оно как то «в лоб»

union bytetochar
{
byte mybyte;
char mychar
};

в 8 строке размер выделяемой памяти под строку раверн нулю, то что у вас потом что то там корректно выводиться - полное везение.

вру - 1 байт выделяется.

1 лайк

Конкретная причина добавки устраняется так:

for (i = 0; i < 3; i++) {
    stFileName[i] = ByteToChar(value_max_temp, i+1);
}
stFileName[3] = 0;
strcat(system_text, stFileName);

stFileName объявленная в разных местах изначально содержит неопределенные данные.
В глобальном пространстве после запуска программы они скорее всего нули. В стеке же большая вероятность схватить не ноль.

Но основная проблема (не проявляется на очень простом вашем примере) - это запись (конкатенация строк) в масив с неподготовленным заранее размером (и в stFileName и в system_text).

Корректность работы ByteToChar оценивать не буду, можете для теста не вычислять, а временно возвращать константный символ.

@Мишутк, а Вас не смутило, что под массив stFileName память не выделена от слова совсем и ТС тупо распахивает всё подряд? Вы ж, вроде, говорили, что не чайник! Не зматили?

@Володька, вот эта запись

означает, что под массив stFileName Вы выделили 1 (один!!!) байт памяти. И куда Вы там пишете никто не знает.

Поведение программы в таких случаях непредсказуемо.

Смутило. Я на это указал.

Но причина мусора - не в этом. Мусор в отсутствующем терминирующем нуле. Его там не окажется и при правильной аллокации памяти.
Смущающий момент вообще приведет к непредсказуемому поведению вплоть до перезагрузок. Но на маленькой простой программе может и работать - после stFileName не объявлено никаких переменных и вся память до стека ничья. Самозахват, так сказать, пока сосед не появился.

Кстати, автору темы “немножко магии”: поменять местами объявление stFileName и system_text - будет чудесный эффект для понимания происходящего.

Большое спасибо за разъяснения, ошибку с объявлением char понял.
Поправил, залил скетч, но вопрос еще остался.

Вариант 1. После заливки плата уходит в вечную перезагрузку

#include <EEPROM.h>

byte address_max_temp = 0; 
byte value_max_temp; 

char system_text[] = "serial:111222\n"
"batch:9\n"
"max_t:";

char stFileName[3] = "";

char ByteToChar(byte byteSt, byte num)
{
  const byte offset = 48;
  if (num==1) return char(offset+byteSt/100);
  if (num==2) return char(offset+byteSt/10-byteSt/100*10);
  if (num==3) return char(offset+byteSt-byteSt/10*10); 
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(5);
  value_max_temp = EEPROM.read(address_max_temp);
  byte i;  

  for (i = 0; i < 3; i++) {
    stFileName[i] = ByteToChar(value_max_temp, i+1);
  }
  strcat(system_text, stFileName);
  Serial.print("MAX TEMP=");
  Serial.println(stFileName);
  Serial.println(system_text);
}

void loop() {}

Вариант 2. Все работает корректно

#include <EEPROM.h>

byte address_max_temp = 0; 
byte value_max_temp; 

char system_text[] = "serial:111222\n"
"batch:9\n"
"max_t:";

char stFileName[] = "000";

char ByteToChar(byte byteSt, byte num)
{
  const byte offset = 48;
  if (num==1) return char(offset+byteSt/100);
  if (num==2) return char(offset+byteSt/10-byteSt/100*10);
  if (num==3) return char(offset+byteSt-byteSt/10*10); 
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(5);
  value_max_temp = EEPROM.read(address_max_temp);
  byte i;  

  for (i = 0; i < 3; i++) {
    stFileName[i] = ByteToChar(value_max_temp, i+1);
  }
  strcat(system_text, stFileName);
  Serial.print("MAX TEMP=");
  Serial.println(stFileName);
  Serial.println(system_text);
}

void loop() {}

Вариант 3. Все работает корректно.

#include <EEPROM.h>

byte address_max_temp = 0; 
byte value_max_temp; 

char system_text[] = "serial:111222\n"
"batch:9\n"
"max_t:";

char ByteToChar(byte byteSt, byte num)
{
  const byte offset = 48;
  if (num==1) return char(offset+byteSt/100);
  if (num==2) return char(offset+byteSt/10-byteSt/100*10);
  if (num==3) return char(offset+byteSt-byteSt/10*10); 
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(5);
  char stFileName[] = "000";
  value_max_temp = EEPROM.read(address_max_temp);
  byte i;  

  for (i = 0; i < 3; i++) {
    stFileName[i] = ByteToChar(value_max_temp, i+1);
  }
  strcat(system_text, stFileName);
  Serial.print("MAX TEMP=");
  Serial.println(stFileName);
  Serial.println(system_text);
}

void loop() {}

Вариант 4. Плата уходит в вечную перезагрузку.

#include <EEPROM.h>

byte address_max_temp = 0; 
byte value_max_temp; 

char system_text[] = "serial:111222\n"
"batch:9\n"
"max_t:";

char ByteToChar(byte byteSt, byte num)
{
  const byte offset = 48;
  if (num==1) return char(offset+byteSt/100);
  if (num==2) return char(offset+byteSt/10-byteSt/100*10);
  if (num==3) return char(offset+byteSt-byteSt/10*10); 
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(5);
  char stFileName[3] = "";
  value_max_temp = EEPROM.read(address_max_temp);
  byte i;  

  for (i = 0; i < 3; i++) {
    stFileName[i] = ByteToChar(value_max_temp, i+1);
  }
  strcat(system_text, stFileName);
  Serial.print("MAX TEMP=");
  Serial.println(stFileName);
  Serial.println(system_text);
}

void loop() {}

Почему-то плата уходит в перезагрузку при объявлении: char stFileName[3] = “” как глобально, так и в setup.

Плохо все, начните с чего попроще.

Согласен с Вами полностью. Дочитал до " Обратите внимания, что при создании массива типа [char], необходим дополнительный элемент массива для нулевого символа."

Изменил длину с 3 на 4: char stFileName[4] = “”;
все заработало и при объявлении в глобал и локально.

Еще раз СПС всем.

Появился теперь вопрос по

char system_text[] = "serial:111222\n"
"batch:9\n"
"max_t:";

Его я объявляю определенной длиной, а потом к нему суммирую stFileName. Это получается тоже неправильно в таком виде?

Заранее необходимо предсказывать размер нужной памяти, тогда и проблем не будет. Или изучать динамическое выделение памяти. Поиском на форуме найдёте массу примеров. + интернет

Судя по приведённым вариантам, нет не понял. А варианты – попытка проскочить методом тыка.

Вот эти темы читать до просветвления:

Ваша основная проблема в том, что вы объявили system_text с заранее установленным размером. И теперь в него добавляете еще символы. Они пишутся поверх других переменных программы.
Чтобы было проще, надо заранее приготовить массив char* заведомо достаточного размера, и потом в него добавлять все нужные вам данные.
Можно сделать хитро:

char system_text[] = "Prefix: AAAAAAAAAAAAAAAAAAAAA";

// в коде функции:
Prefix[8] = 0; //позиция первого А
//дальше можно strcat в массив system_text

Массив будет готов принять еще столько символов, сколько остается после позиции установки 0 (в примере поставили в 8). Пространство заполненное символом “А” доступно для записи новой информации. Не забывать, что в конце значимого текста нужно установить 0, иначе если просто писать одиночные символы, в конце строки еще будут остаточные ААА…

сами себя перехитрили.

Не проще так:

char system_text[30] = "Prefix: ";

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

Это для понимания. Как вы написали - правильно. Но что после Prefix будет в массиве - не известно. Автор отладится когда там 0, а в бою замусорится без инициализации и пойдут глюки.
Когда там заведомо не 0, внимание будет обращено на необходимость терминирования последовательности.

В первоначальном коде есть складывание посимвольно. Как бы сама тема именно оттуда и выросла. Так-то можно без мучений и sprintf по классике делать для вывода int в строку, зачем писать свою функцию.

Нельзя. Вернее, можно, но не стоит. При таком заходе у Вас литерал остаётся в памяти, а ресурсов-то и так кот наплакал. Не тяните в крохотные микроконтроллеры свои “гигабайтные привычки”.