Возврат из функции строки символов

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Добрый день, камрады.

Возникла необходимость возвращать из функции текст. В данный момент реализовал это так (скетч работает, но меня не покидает ощущение, что сделано это не лучшим образом).

const char ml_1[] PROGMEM = "January";
const char ml_2[] PROGMEM = "February";
const char ml_3[] PROGMEM = "March";
const char ml_4[] PROGMEM = "April";
const char ml_5[] PROGMEM = "May";
const char ml_6[] PROGMEM = "June";
const char ml_7[] PROGMEM = "July";
const char ml_8[] PROGMEM = "August";
const char ml_9[] PROGMEM = "September";
const char ml_10[] PROGMEM = "October";
const char ml_11[] PROGMEM = "November";
const char ml_12[] PROGMEM = "December";

const char* const months_names[] PROGMEM = {
  ml_1, ml_2, ml_3, ml_4, ml_5, ml_6, ml_7, ml_8, ml_9, ml_10, ml_11, ml_12
};

void setup() {
  Serial.begin(115200);
}

void loop() {
  for (uint8_t i = 0; i < 12; i++) {
    String arrayBuf = tprint(i);
    Serial.print(F("i - "));
    Serial.println(arrayBuf);
    delay(1000);
  }
}

String tprint(uint8_t val) {
  char arrayBuf[strlen_P(pgm_read_ptr_near(months_names + val))];
  strcpy_P(arrayBuf, pgm_read_ptr_near(months_names + val));
  return (String)(arrayBuf);
}

Прошу опытных товарищей подсказать, как правильно реализовать подобную конструкцию.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Не прошло и недели: http://arduino.ru/forum/obshchii/rezultat-funktsii-stroka

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

andriano пишет:
Не прошло и недели: http://arduino.ru/forum/obshchii/rezultat-funktsii-stroka

Спасибо, читал, но за всеми dtostrf и strcat мысль как то затерялась, уровень у вопрошающего гораздо выше моего, хотя я тоже с ссылками и указателями разобраться не могу (вроде и понимаю что указатель нужен для доступа к переменной, но не могу понять "а зачем?", если к переменной доступ итак есть. какая выгода работать с ней через указатель, а не напрямую?).

Можете тезисно объяснить как это правильно должно быть реализовано? Сейчас на мой взгляд происходит следующее (заранее извиняюсь если не понимаю элементарных вещей - учусь, но не все дается с первого раза):

1. Внутри функции создается массив arrayBuf фиксированного размера и в него копируется содержимое соответствующего массива из Progmem.

2. Затем содержимое массива arrayBuf конвертируется в строку и возвращается.

Что меня смущает - массив arrayBuf объявлен внутри функции, поэтому после return arrayBuf перестает существовать. Поэтому указатель на него вернуть в функцию (в том виде что написано у меня) невозможно. А возврат сроки допустим (массив arrayBuf перестанет существовать уже после того, как вызывающий функцию получит строку через return), но насколько я понимаю, такой способ не оптимален в плане использования ресурсов мк. 

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Все верно - жонглирование со стрингами неоптимально. В стеснённых условиях единственно нормально выглядящий способ - помещать данные сразу в область памяти, непосредственно используемую для дальнейшей работы. Т.е. сразу из функции лупить на дисплей или использовать внешний (по отношению к функции) буфер (массив). Адрес области памяти, выделенного под таковой буфер как раз указателем и передается.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

sadman41 пишет:
Все верно - жонглирование со стрингами неоптимально. В стеснённых условиях единственно нормально выглядящий способ - помещать данные сразу в область памяти, непосредственно используемую для дальнейшей работы. Т.е. сразу из функции лупить на дисплей или использовать внешний (по отношению к функции) буфер (массив). Адрес области памяти, выделенного под таковой буфер как раз указателем и передается.

Позвольте "поесть" Ваш мозг еще немного - сразу выплевывать это на дисплей пока не вариант, а вот про "использовать внешний (по отношению к функции) буфер (массив)" можно подробнее? Вы имеете ввиду глобальную переменную (в чем тогда профит, ведь она будет занимать память постоянно, плюс непонятно как ее объявить без указания размера) или что то иное? Еще вопросик - я копирую в arrayBuf из progmem значение и затем его отдаю вызывающему (то есть получается куча операций с довольно приличным объемом данных)... но вероятно есть способ (и возможно это правильнее) передавать вызывающему ссылку на массив progmem (не то чтобы я спрашиваю разрешение сделать это, просто рассуждаю вслух...)? Или это так не работает?

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Внешняя - это не обязательно глобальная.

В функции showData() создали буфер на 40 символов, передали его в функцию formData, там накопировали чего хотели в буфер. Потом напечатали и завершили функцию showData(). Буфер автоматически прибился, память вернулась в систему.

А если и глобальная - что вы ее как Гитлера боитесь? Я, к примеру, выделил глобальный буфер на 200 и юзаю его для входящих данных, вывода на дисплей и формирования пакета, который в сеть выплевываю. Главное - не портить в нем данные, пока они не использованы.

И зачем "без объявления размера“? LCD внезапно, в 00:00 начнет принимать строки в 800кб и отображать их на всю ширину?

Перестаньте придумывать функции "на всякий случай, неизвестную длину". Пишите под конкретную задачу. С практикой уже сами начнёте "на автомате" делать универсальные.

Указатель на массив PROGMEM ничем не хуже другого указателя. Если принимающая функция понимает, что это именно PROGMEM и для доступа нужен pgm_read() то работать это будет так же стабильно.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Кстати, я погорячился - вариант со String тоже не сильно работоспособен оказался в реальной задаче, видимо arrayBuf уничтожается при выходе из функции, и то что оно работало в тестовом файле только благодаря тому, что ничто другое не затирало освободившуюся память :( На данный момент тестирую такой вариант:

const char ml_1[] PROGMEM = "January";
const char ml_2[] PROGMEM = "February";
const char ml_3[] PROGMEM = "March";
const char ml_4[] PROGMEM = "April";
const char ml_5[] PROGMEM = "May";
const char ml_6[] PROGMEM = "June";
const char ml_7[] PROGMEM = "July";
const char ml_8[] PROGMEM = "August";
const char ml_9[] PROGMEM = "September";
const char ml_10[] PROGMEM = "October";
const char ml_11[] PROGMEM = "November";
const char ml_12[] PROGMEM = "December";
const char ms_1[] PROGMEM = "JAN";
const char ms_2[] PROGMEM = "FEB";
const char ms_3[] PROGMEM = "MAR";
const char ms_4[] PROGMEM = "APR";
const char ms_5[] PROGMEM = "MAY";
const char ms_6[] PROGMEM = "JUN";
const char ms_7[] PROGMEM = "JUL";
const char ms_8[] PROGMEM = "AUG";
const char ms_9[] PROGMEM = "SEP";
const char ms_10[] PROGMEM = "OCT";
const char ms_11[] PROGMEM = "NOV";
const char ms_12[] PROGMEM = "DEC";

const char* const months_names[] PROGMEM = {
  ml_1, ml_2, ml_3, ml_4, ml_5, ml_6, ml_7, ml_8, ml_9, ml_10, ml_11, ml_12,
  ms_1, ms_2, ms_3, ms_4, ms_5, ms_6, ms_7, ms_8, ms_9, ms_10, ms_11, ms_12
};

char* arrayBuf;

void setup() {
  Serial.begin(115200);
}

void loop() {
  for (uint8_t i = 0; i < 24; i++) {
    Serial.print(i);
    Serial.print(F(" - "));
    Serial.println(monthToString(i));
    delay(500);
  }
}

char* monthToString(uint8_t val) {
  free(arrayBuf);
  uint8_t sizeBuf = strlen_P(pgm_read_ptr_near(months_names + val));
  arrayBuf = (char*) malloc(sizeBuf);
  strcpy_P(arrayBuf, pgm_read_ptr_near(months_names + val));
  return arrayBuf;
}

Вроде работает

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

sadman41 пишет:
Внешняя - это не обязательно глобальная. В функции showData() создали буфер на 40 символов, передали его в функцию formData, там накопировали чего хотели в буфер. Потом напечатали и завершили функцию showData(). Буфер автоматически прибился, память вернулась в систему.

Можете носом ткнуть где про это почитать (в контексте массивов char) или в работающий пример? Пытался это осуществить, но без примера осмыслить и осуществить не получается.

sadman41 пишет:
А если и глобальная - что вы ее как Гитлера боитесь?

Не то чтобы боюсь, но стараюсь без крайней необходимости не разводить их. Идея с выделением глобального буфера мне в голову не приходила, спасибо за идею. Действительно, это может избавить от лишней передачи данных между функциями. И да, Вы правы, пытаюсь сделать универсальную функцию, но видимо пока рановато )

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

sadman41 пишет:
Указатель на массив PROGMEM ничем не хуже другого указателя. Если принимающая функция понимает, что это именно PROGMEM и для доступа нужен pgm_read() то работать это будет так же стабильно.
  +1

Dinosaur или возвращайте String.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

qwone пишет:

или возвращайте String.

Можете скетч в первом  примере глянуть где у меня ошибка? В том виде в каком он представлен - все работает, но стоит вынести его в реальный проект - лезет разный мусор на экран :(

rkit
Offline
Зарегистрирован: 23.11.2016

Прочитай как работают строки в C. И прочитай как конструируется String в ардуино.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016
String(const __FlashStringHelper *str); // конструктор для flash-строк
Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

rkit пишет:

Прочитай как работают строки в C. И прочитай как конструируется String в ардуино.

Ткните пожалуйста носом в нужный учебник/статью (желательно уровня для новичков).

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Dinosaur пишет:

...но не могу понять "а зачем?", если к переменной доступ итак есть. какая выгода работать с ней через указатель, а не напрямую?).

Так через указатель - это и есть напрямую.

Передать параметр в функцию можно двумя способами: по значению и по адресу. Если передается по значению, то в памяти создается копия переменной и она передается в функцию. Соответственно, если функция меняет переданный параметр, это изменение происходит только в копии, а оригинал остается неизменным. Если же передается по адресу, то функция знает, где лежит оригинал переменной и может его изменять. Т.е. работать с ним напрямую.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

void showDate() {
char buff[40];
...
formatDate(i, buff, sizeof(buf));
...
lcd.print(buff);
}

Вот и все, без стрингов и глобалов.

Между прочим - я не советовал глобальный буфер для обмена использовать. Это, как раз, порочная практика. Я его использую для подготовки данных непосредственно перед выводом.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

sadman41 пишет:
void showDate() { char buff[40]; ... formatDate(i, buff, sizeof(buf)); ... lcd.print(buff); } Вот и все, без стрингов и глобалов. Между прочим - я не советовал глобальный буфер для обмена использовать. Это, как раз, порочная практика. Я его использую для подготовки данных непосредственно перед выводом.

Идею понял, спасибо, положу в копилку знаний. Теперь осталось разобраться с прогмемами, стрингами и указателями разными. Из принципа. Никак они не даются мне.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

andriano пишет:
Передать параметр в функцию можно двумя способами: по значению и по адресу. Если передается по значению, то в памяти создается копия переменной и она передается в функцию. Соответственно, если функция меняет переданный параметр, это изменение происходит только в копии, а оригинал остается неизменным. Если же передается по адресу, то функция знает, где лежит оригинал переменной и может его изменять. Т.е. работать с ним напрямую.

То есть внутри функции Func1 объявляем переменную byte per (она занимает 1 байт памяти), из этой функции вызываем Func2 (void) и передаем указатель на per в качестве аргумента, Func2 по полученному указателю может читать и изменять per (несмотря на то что per объявлена не глобально, а внутри Func1), при этом в памяти per занимает все тот же один байт (поскольку копия в Func2 не создавалась), по завершении Func2 возвращаемся в Func1 где нам доступна измененная per. По выходу из Func1, переменная per удаляется из памяти. Я верно понял?

MaksVV
Offline
Зарегистрирован: 06.08.2015

Dinosaur пишет:

Можете скетч в первом  примере глянуть где у меня ошибка? В том виде в каком он представлен - все работает, но стоит вынести его в реальный проект - лезет разный мусор на экран :(

вы сами ответили на этот вопрос, (см ниже цитату).  Только вот утверждение жирным шрифтом, имхо, не правильное. Видимо массив перестает существовать до его использования. Квалификатор static массива arrayBuf решит эту проблему, т.к. массив уничтожаться не будет при выходе из функции, но, разумеется, таким примером со String лучше не пользоваться. как нужно сделать уже объяснили.

Dinosaur пишет:
Что меня смущает - массив arrayBuf объявлен внутри функции, поэтому после return arrayBuf перестает существовать. Поэтому указатель на него вернуть в функцию (в том виде что написано у меня) невозможно. А возврат сроки допустим (массив arrayBuf перестанет существовать уже после того, как вызывающий функцию получит строку через return) 

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

Да, с возвратом строки я явно погорячился. Но все же как сделать правильно, до меня не доходит. Вот максимально кастрированный кусок глючного скетча (часть мусора пришлось оставить, чтобы в память срал) с комментариями:


const char m_s_1[] PROGMEM = "JAN";
const char m_s_2[] PROGMEM = "FEB";
const char m_s_3[] PROGMEM = "MAR";
const char m_s_4[] PROGMEM = "APR";
const char m_s_5[] PROGMEM = "MAY";
const char m_s_6[] PROGMEM = "JUN";
const char m_s_7[] PROGMEM = "JUL";
const char m_s_8[] PROGMEM = "AUG";
const char m_s_9[] PROGMEM = "SEP";
const char m_s_10[] PROGMEM = "OCT";
const char m_s_11[] PROGMEM = "NOV";
const char m_s_12[] PROGMEM = "DEC";
const char* const month_names[] PROGMEM = {m_s_1, m_s_2, m_s_3, m_s_4, m_s_5, m_s_6, m_s_7, m_s_8, m_s_9, m_s_10, m_s_11, m_s_12};

struct time {
  uint8_t seconds: 6;
  uint8_t minutes: 6;
  uint8_t hours: 5;
  uint8_t dow: 3;
  uint8_t date: 5;
  uint8_t month: 4;
  uint16_t year: 12;
};

time newTime;

void setup() {
  newTime.year = 2000;
  Serial.begin(115200);
}

void loop() {
  uint32_t actualTime = millis();
  static uint32_t lastChangeTime;
  if (actualTime - lastChangeTime > 500) {
    static uint32_t num;
    String printstring = (String)num + F(" passed");
    Serial.println(printstring);
    num++;
    lastChangeTime = actualTime;
    changeTime();
    printTime();
  }
}

void changeTime() {
  newTime.seconds++;
  if (newTime.seconds > 59) {
    newTime.seconds = 1;
  }
  newTime.minutes++;
  if (newTime.minutes > 59) {
    newTime.minutes = 1;
  }
  newTime.hours++;
  if (newTime.hours > 23) {
    newTime.hours = 1;
  }
  newTime.dow++;
  if (newTime.dow > 6) {
    newTime.dow = 0;
  }
  newTime.date++;
  if (newTime.date > 31) {
    newTime.date = 1;
  }
  newTime.month++;
  if (newTime.month > 12) {
    newTime.month = 1;
  }
  newTime.year++;
  if (newTime.year > 2100) {
    newTime.year = 20100;
  }
}

void printTime() {
  String displayString = ((String)F("Time:") + (String)newTime.hours + F(":") + (String)newTime.minutes + F(":") + (String)newTime.seconds);
  Serial.println(displayString);
   displayString = ((String)F("Date:") + (String)newTime.date + F(" ") + getMonthShort() + F(" ") + (String)newTime.year + F(" ") + (String)newTime.dow);
  Serial.println(displayString);
  Serial.println("---"); 
}

String getMonthShort() {
  uint8_t idx = (getMonth() - 1);
  char arrayBuf[strlen_P(pgm_read_ptr_near(month_names + idx))]; // длина массива рассчитывается правильно
  strcpy_P(arrayBuf, pgm_read_ptr_near(month_names + idx));
  Serial.print("arrayBuf - ");
  Serial.println(arrayBuf);                                      // и вот тут даже печатается правильно
  //return (String)(idx);                                        // и в таком варианте не виснет и не перезагружается
  return (String)(arrayBuf);                                     // но стоит вернуть arrayBuf, как все виснет/перегружается
}

uint8_t getMonth() {
  return newTime.month;
}

То есть ошибка возникает в функции getMonthShort при попытке вернуть arrayBuf (видимо я как то не так это делаю). Усиленно пытаюсь понять что все написали, но не могли бы в этом примере ткнуть носом - где я неправ. Подозреваю что возвращаю я все же не строку, а что то совсем не то...

sadman41
Онлайн
Зарегистрирован: 19.10.2016

И чему равна размерность arrayBuf ?

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

sadman41 пишет:
И чему равна размерность arrayBuf ?

3.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

А скоко нужно выделять памяти под строку в Си? Подсказываю: иначе они называются ASCIIZ строки и все строковые функции в Си работают корректно только с ASCIIZ строками.

PS. String - не строка, а класс в Wiring

MaksVV
Offline
Зарегистрирован: 06.08.2015
Пусть фунция возвращает не String а сразу указатель на bufarray. Нах этот String не пойму. 
char * stringMonth()
{
static char bufArray[20];
// тут заполняем bufArray
return  bufArray;
}

 

 
Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

М... +1?

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

MaksVV пишет:
Пусть фунция возвращает не String а сразу указатель на bufarray. Нах этот String не пойму.

А зачем постоянно висящий в памяти bufArray? На данный момент я решил задачу через malloc (хотя по факту память занята до следующего вызова функции), но хочется все же разобраться с передачей/возвратом char/string.

char* monthToString(uint8_t val) {
  free(arrayBuf);
  uint8_t sizeBuf = strlen_P(pgm_read_ptr_near(months_names + val));
  arrayBuf = (char*) malloc(sizeBuf);
  strcpy_P(arrayBuf, pgm_read_ptr_near(months_names + val));
  return arrayBuf;
}

О, и вопрос к sadman41 - тут тоже размер буфера 3, но все работает...

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

sadman41 пишет:
А скоко нужно выделять памяти под строку в Си? Подсказываю: иначе они называются ASCIIZ строки и все строковые функции в Си работают корректно только с ASCIIZ строками. PS. String - не строка, а класс в Wiring

Fuck, при размере буфера +1 заработало (в первом приближении). Завтра буду еще проверять. Но почему при использовании malloc и размере буфера 3 все работает?

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Случайное совпадение: память ещё не была ранее засрана и в позиции +1 находился 0x00, он же \0, он же Z-терминатор строки.

Немного иное распределение памяти при компиляции, работа чуть подольше - в позиции +1 может оказаться что угодно и функция, которая ищет его для определения конца строки, будет искать по всей памяти, до второго пришествия.

С маллоками я бы не советовал вам пока связываться. До тех пор, пока не поймёте как распределяется память под переменную и как работают функции с памятью.

rkit
Offline
Зарегистрирован: 23.11.2016

MaksVV пишет:

Пусть фунция возвращает не String а сразу указатель на bufarray. Нах этот String не пойму. 
char * stringMonth()
{
static char bufArray[20];
// тут заполняем bufArray
return  bufArray;
}

 

 

А если 10 раз функцию вызвать - где-то в памяти будет висеть 10 вариантов bufArray?

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Будет висеть один экземпляр на 20 байт.
Но если такая функция, например, попадет под template - это будет гульба на все деньги.

rkit
Offline
Зарегистрирован: 23.11.2016

Гульба будет, когда незнающий программист попробует вызвать функцию 10 раз, возвращая результаты в разные переменные, и вместо общепринятого поведения функции получит 10 копий одного и того же. Конечно это не очень сильно относится к самописному коду, но писать лучше по правилам.  А по правилам либо передают буфер как аргумент, либо возвращают String.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Так 10 вариантов или 10 копий одного и того же (последнего) варианта?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

sadman41 пишет:
Так 10 вариантов или 10 копий одного и того же (последнего) варианта?

Возьмите и проверьте. Но скорее всего одна копия.Код во флеш, статические данные в ОЗУ. А где реально все лежит определит компилятор. То есть во время компиляции оперативка уйдет на это безобразие.

MaksVV
Offline
Зарегистрирован: 06.08.2015

Да я не на статик акцентировал внимание, а на то что ТС каждый байт пытается сэкономить, а сам String использует направо и налево. Ну пусть глобальный буфер сделает или локальный из вызывающей функции, Sadman ведь уже советовал. В этом случае можно даж указатель не возвращать на буфер. А просто фунция void обновляет буфер и все. 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

MaksVV,ТС даже String не может пользоваться. Хочет экономить память, то пусть вернет String правой ссылкой.Распечатает и освободит память из кучи с помощью скобки.} Вот только что бы это понять надо получить базовые знания. А если их нет, то объянять долго и бесполезно.

MaksVV
Offline
Зарегистрирован: 06.08.2015

sadman41 пишет:
Но если такая функция, например, попадет под template - это будет гульба на все деньги.

Объясни , если не трудно, что это означает

MaksVV
Offline
Зарегистрирован: 06.08.2015

rkit пишет:

Гульба будет, когда незнающий программист попробует вызвать функцию 10 раз, возвращая результаты в разные переменные, и вместо общепринятого поведения функции получит 10 копий одного и того же. Конечно это не очень сильно относится к самописному коду, но писать лучше по правилам.  А по правилам либо передают буфер как аргумент, либо возвращают String.

я вообще не понял к чему это. У нас результат указатель на массив. Вызвали эту функцию, получили указатель, в данном случае распечатали массив по указателю на лсд.   Каждый вызов сразу используем, какие еще копии. Или я чего то не понимаю. Но то что лучше так не делать это я уже понял, а вот почему, еще не очень

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Ну дам решение.И что это спасет ТС. Скорее всего нет

const char ml_1[] PROGMEM = "January";
const char ml_2[] PROGMEM = "February";
const char ml_3[] PROGMEM = "March";
const char ml_4[] PROGMEM = "April";
const char ml_5[] PROGMEM = "May";
const char ml_6[] PROGMEM = "June";
const char ml_7[] PROGMEM = "July";
const char ml_8[] PROGMEM = "August";
const char ml_9[] PROGMEM = "September";
const char ml_10[] PROGMEM = "October";
const char ml_11[] PROGMEM = "November";
const char ml_12[] PROGMEM = "December";

const char* const months_names[] PROGMEM = {
  ml_1, ml_2, ml_3, ml_4, ml_5, ml_6, ml_7, ml_8, ml_9, ml_10, ml_11, ml_12
};

void setup() {
  Serial.begin(115200);
}

void loop() {
  for (uint8_t i = 0; i < 12; i++) {
    Serial.print(F("i - "));
    Serial.println(
      String((const __FlashStringHelper *)pgm_read_word(months_names + i))
    );
    delay(1000);
  }
}

 

sadman41
Онлайн
Зарегистрирован: 19.10.2016

MaksVV пишет:

Объясни , если не трудно, что это означает


Без примера будет сложновато, а примеры с мобилы не понатыкиваешь.

Есть такая рутина - EEPROM.put(). Принимает на вход переменные стандартных типов и структуры нестандартные. Внутри себя делает цикл от 0 до sizeof() параметра и побайтово сторит. Но как она управляется с любым типом - ведь универсального нет? А все просто - нет функции EEPROM.put(float) изначально. Есть шаблон, из которого компилятор сгенерирует функцию для float, если таковой вызов будет замечен в исходнике. И для int создаст. И для myStruct. Каждому типу - отдельная функция. Типы byte array[5] и byte array[6] тоже разные, к примеру.

В итоге имеем штук 20 практически одинаковых функций. И тут мы решили, что нам срочно нужен static char[20] внутри . Ну, как буфер. Суем объявление в темплейт, компилятор размножает функции и хренак - мы одним движением минусанули 400байт, которые не освободить, ни реюзнуть не можем - область видимости, однако, мешает.

Набили, короче, багажник воздушными шарами и ездим.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

MaksVV пишет:

Но то что лучше так не делать это я уже понял, а вот почему, еще не очень


Решил ты напечатать три месяца рождения - свой, жены и мамы. Но не просто, а весело - за один прием. Типа "Декапрелеюнь". Сделал три запроса в функцию, каждый результат в свою переменную char * поместил... Думаешь, что в первой будет "Декабрь", во второй "Апрель", в третьей "Июнь"?

MaksVV
Offline
Зарегистрирован: 06.08.2015

пасиб , Про шаблоны не знал. Почитаю. Ппц когда уже закончится период времени, когда я регулярно узнаю то , что я даже не слышал в этом языке. я уж не говорю про разобраться в этом новом...

MaksVV
Offline
Зарегистрирован: 06.08.2015

Нет конечно. Цель была при каждом вызове сразу печатать. Типа так

sadman41 пишет:
В стеснённых условиях единственно нормально выглядящий способ - помещать данные сразу в область памяти, непосредственно используемую для дальнейшей работы. Т.е. сразу из функции лупить на дисплей....

MaksVV
Offline
Зарегистрирован: 06.08.2015

Del

sadman41
Онлайн
Зарегистрирован: 19.10.2016

MaksVV пишет:
Нет конечно. Цель была при каждом вызове сразу печатать

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

MaksVV
Offline
Зарегистрирован: 06.08.2015

Ок, намотал

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

MaksVV пишет:
пасиб , Про шаблоны не знал. Почитаю. Ппц когда уже закончится период времени, когда я регулярно узнаю то , что я даже не слышал в этом языке. я уж не говорю про разобраться в этом новом...
Ну здесь надо понять что Ложки нетhttps://www.youtube.com/watch?v=tHQr6qLJeWE

По факту язык Си/Си+ так же далек от программиста, как и от камня. Просто это удобный инструмент для описания сложных механизмов не менее сложными программами.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

sadman41 пишет:
Случайное совпадение: память ещё не была ранее засрана и в позиции +1 находился 0x00, он же \0, он же Z-терминатор строки. Немного иное распределение памяти при компиляции, работа чуть подольше - в позиции +1 может оказаться что угодно и функция, которая ищет его для определения конца строки, будет искать по всей памяти, до второго пришествия.

Возможно не все так однозначно: сидел мучал сегодня скетч, обратил внимание что если размер буфера задаю равным количеству символов, но строку 80 скетча (из поста 19) излагаю в следующей редакции:

displayString = ((String)F("Date:") + newTime.date + F(" ") + getMonthShort() + F(" ") + newTime.year + F(" ") + newTime.dow);

(то есть убираю лишние преобразования в String), то скетч работает без глюков, причем довольно долго (сколько у меня терпения хватает). Вариантов два - либо опять случайность, либо мне срочно нужно почитать про работу String (собственно чем и занимаюсь последние сутки).

sadman41 пишет:
С маллоками я бы не советовал вам пока связываться. До тех пор, пока не поймёте как распределяется память под переменную и как работают функции с памятью.

Ну это я сгоряча сделал, метод тыка в поисках рабочего решения :)

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

MaksVV пишет:

Да я не на статик акцентировал внимание, а на то что ТС каждый байт пытается сэкономить, а сам String использует направо и налево. Ну пусть глобальный буфер сделает или локальный из вызывающей функции, Sadman ведь уже советовал. В этом случае можно даж указатель не возвращать на буфер. А просто фунция void обновляет буфер и все. 

ЦЕЛЬ экономии каждого байта не стоит, но стараюсь (по возможности, и в меру своих знаний) к памяти относиться бережно (а то и блинком можно всю память занять при желании). String использую потому что:

а) мне это более менее знакомо

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

displayString = ((String)F("Date:") + newTime.date + F(" ") + getMonthShort() + F(" ") + newTime.year + F(" ") + newTime.dow);
Serial.println(displayString);

т.е. идея такова: вызываю функцию в которой формирую строку для вывода на экран  (при этом вызвав из нее функции преобразования числовых значений в текстовые), отправить строку на экран и покинуть функцию. Соответственно память, занятая под преобразование числового значения в текст освободится в тот момент, когда выйдем из функции преобразования, а память занятая в функции вывода на экран освободится по выходу из этой функции. По крайней мере я так представляю работу того, что я понаписал в скетче (сообщение 19). Если я ошибаюсь - прошу меня поправить в рассуждениях.

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

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

qwone пишет:

ТС даже String не может пользоваться. Хочет экономить память, то пусть вернет String правой ссылкой. Распечатает и освободит память из кучи с помощью скобки.} Вот только что бы это понять надо получить базовые знания. А если их нет, то объянять долго и бесполезно.

Я честно пытаюсь, пытаюсь понять умных человеков. Проблема, как Вы верно заметили, в отсутствии у меня базовых знаний. Вы объясняете элементарные, на Ваш взгляд, вещи исходя из своего уровня, а я со своего уровня из воспринимаю их как татарскую грамоту. Я был бы признателен, если бы ткнули меня носом туда, где можно почерпнуть знания, необходимые для понимания написанного Вами в этой ветке. Все что вижу непонятного сразу пытаюсь загуглить как оно работает, но не всегда доходит с первого раза. Да и со второго тоже.

P.S. Что есть "правая ссылка"? 

P.P.S. про фокус со "скобками" для освобождения памяти я в курсе, гляньте пожалуйста скетч в сообщении 19, мне кажется что выделив работу со стрингами в отдельные функции printTime() и getMonthShort() я как раз и добился этого эффекта.

Dinosaur
Dinosaur аватар
Offline
Зарегистрирован: 01.01.2018

qwone пишет:
Ну дам решение. И что это спасет ТС. Скорее всего нет

Спасибо за пример, усиленно сижу и пытаюсь разобраться что такое const __FlashStringHelper и с чем его нужно злоупотреблять. Пока не понял :(

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

MaksVV пишет:
пасиб , Про шаблоны не знал. Почитаю. Ппц когда уже закончится период времени, когда я регулярно узнаю то , что я даже не слышал в этом языке. я уж не говорю про разобраться в этом новом...
Учебник читать не пробовал?