Возврат из функции строки символов
- Войдите на сайт для отправки комментариев
Сб, 07/11/2020 - 08:33
Добрый день, камрады.
Возникла необходимость возвращать из функции текст. В данный момент реализовал это так (скетч работает, но меня не покидает ощущение, что сделано это не лучшим образом).
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); }
Прошу опытных товарищей подсказать, как правильно реализовать подобную конструкцию.
Возвращаемый функцией текст будет использоваться для формирования строки, выводимой на дисплей.
Не прошло и недели: http://arduino.ru/forum/obshchii/rezultat-funktsii-stroka
Спасибо, читал, но за всеми dtostrf и strcat мысль как то затерялась, уровень у вопрошающего гораздо выше моего, хотя я тоже с ссылками и указателями разобраться не могу (вроде и понимаю что указатель нужен для доступа к переменной, но не могу понять "а зачем?", если к переменной доступ итак есть. какая выгода работать с ней через указатель, а не напрямую?).
Можете тезисно объяснить как это правильно должно быть реализовано? Сейчас на мой взгляд происходит следующее (заранее извиняюсь если не понимаю элементарных вещей - учусь, но не все дается с первого раза):
1. Внутри функции создается массив arrayBuf фиксированного размера и в него копируется содержимое соответствующего массива из Progmem.
2. Затем содержимое массива arrayBuf конвертируется в строку и возвращается.
Что меня смущает - массив arrayBuf объявлен внутри функции, поэтому после return arrayBuf перестает существовать. Поэтому указатель на него вернуть в функцию (в том виде что написано у меня) невозможно. А возврат сроки допустим (массив arrayBuf перестанет существовать уже после того, как вызывающий функцию получит строку через return), но насколько я понимаю, такой способ не оптимален в плане использования ресурсов мк.
Все верно - жонглирование со стрингами неоптимально. В стеснённых условиях единственно нормально выглядящий способ - помещать данные сразу в область памяти, непосредственно используемую для дальнейшей работы. Т.е. сразу из функции лупить на дисплей или использовать внешний (по отношению к функции) буфер (массив). Адрес области памяти, выделенного под таковой буфер как раз указателем и передается.
Позвольте "поесть" Ваш мозг еще немного - сразу выплевывать это на дисплей пока не вариант, а вот про "использовать внешний (по отношению к функции) буфер (массив)" можно подробнее? Вы имеете ввиду глобальную переменную (в чем тогда профит, ведь она будет занимать память постоянно, плюс непонятно как ее объявить без указания размера) или что то иное? Еще вопросик - я копирую в arrayBuf из progmem значение и затем его отдаю вызывающему (то есть получается куча операций с довольно приличным объемом данных)... но вероятно есть способ (и возможно это правильнее) передавать вызывающему ссылку на массив progmem (не то чтобы я спрашиваю разрешение сделать это, просто рассуждаю вслух...)? Или это так не работает?
Внешняя - это не обязательно глобальная.
В функции showData() создали буфер на 40 символов, передали его в функцию formData, там накопировали чего хотели в буфер. Потом напечатали и завершили функцию showData(). Буфер автоматически прибился, память вернулась в систему.
А если и глобальная - что вы ее как Гитлера боитесь? Я, к примеру, выделил глобальный буфер на 200 и юзаю его для входящих данных, вывода на дисплей и формирования пакета, который в сеть выплевываю. Главное - не портить в нем данные, пока они не использованы.
И зачем "без объявления размера“? LCD внезапно, в 00:00 начнет принимать строки в 800кб и отображать их на всю ширину?
Перестаньте придумывать функции "на всякий случай, неизвестную длину". Пишите под конкретную задачу. С практикой уже сами начнёте "на автомате" делать универсальные.
Указатель на массив PROGMEM ничем не хуже другого указателя. Если принимающая функция понимает, что это именно PROGMEM и для доступа нужен pgm_read() то работать это будет так же стабильно.
Кстати, я погорячился - вариант со String тоже не сильно работоспособен оказался в реальной задаче, видимо arrayBuf уничтожается при выходе из функции, и то что оно работало в тестовом файле только благодаря тому, что ничто другое не затирало освободившуюся память :( На данный момент тестирую такой вариант:
Вроде работает
Можете носом ткнуть где про это почитать (в контексте массивов char) или в работающий пример? Пытался это осуществить, но без примера осмыслить и осуществить не получается.
Не то чтобы боюсь, но стараюсь без крайней необходимости не разводить их. Идея с выделением глобального буфера мне в голову не приходила, спасибо за идею. Действительно, это может избавить от лишней передачи данных между функциями. И да, Вы правы, пытаюсь сделать универсальную функцию, но видимо пока рановато )
Dinosaur или возвращайте String.
или возвращайте String.
Можете скетч в первом примере глянуть где у меня ошибка? В том виде в каком он представлен - все работает, но стоит вынести его в реальный проект - лезет разный мусор на экран :(
Прочитай как работают строки в C. И прочитай как конструируется String в ардуино.
Прочитай как работают строки в C. И прочитай как конструируется String в ардуино.
Ткните пожалуйста носом в нужный учебник/статью (желательно уровня для новичков).
...но не могу понять "а зачем?", если к переменной доступ итак есть. какая выгода работать с ней через указатель, а не напрямую?).
Передать параметр в функцию можно двумя способами: по значению и по адресу. Если передается по значению, то в памяти создается копия переменной и она передается в функцию. Соответственно, если функция меняет переданный параметр, это изменение происходит только в копии, а оригинал остается неизменным. Если же передается по адресу, то функция знает, где лежит оригинал переменной и может его изменять. Т.е. работать с ним напрямую.
void showDate() {
char buff[40];
...
formatDate(i, buff, sizeof(buf));
...
lcd.print(buff);
}
Вот и все, без стрингов и глобалов.
Между прочим - я не советовал глобальный буфер для обмена использовать. Это, как раз, порочная практика. Я его использую для подготовки данных непосредственно перед выводом.
Идею понял, спасибо, положу в копилку знаний. Теперь осталось разобраться с прогмемами, стрингами и указателями разными. Из принципа. Никак они не даются мне.
То есть внутри функции Func1 объявляем переменную byte per (она занимает 1 байт памяти), из этой функции вызываем Func2 (void) и передаем указатель на per в качестве аргумента, Func2 по полученному указателю может читать и изменять per (несмотря на то что per объявлена не глобально, а внутри Func1), при этом в памяти per занимает все тот же один байт (поскольку копия в Func2 не создавалась), по завершении Func2 возвращаемся в Func1 где нам доступна измененная per. По выходу из Func1, переменная per удаляется из памяти. Я верно понял?
Можете скетч в первом примере глянуть где у меня ошибка? В том виде в каком он представлен - все работает, но стоит вынести его в реальный проект - лезет разный мусор на экран :(
вы сами ответили на этот вопрос, (см ниже цитату). Только вот утверждение жирным шрифтом, имхо, не правильное. Видимо массив перестает существовать до его использования. Квалификатор static массива arrayBuf решит эту проблему, т.к. массив уничтожаться не будет при выходе из функции, но, разумеется, таким примером со String лучше не пользоваться. как нужно сделать уже объяснили.
Да, с возвратом строки я явно погорячился. Но все же как сделать правильно, до меня не доходит. Вот максимально кастрированный кусок глючного скетча (часть мусора пришлось оставить, чтобы в память срал) с комментариями:
То есть ошибка возникает в функции getMonthShort при попытке вернуть arrayBuf (видимо я как то не так это делаю). Усиленно пытаюсь понять что все написали, но не могли бы в этом примере ткнуть носом - где я неправ. Подозреваю что возвращаю я все же не строку, а что то совсем не то...
И чему равна размерность arrayBuf ?
3.
А скоко нужно выделять памяти под строку в Си? Подсказываю: иначе они называются ASCIIZ строки и все строковые функции в Си работают корректно только с ASCIIZ строками.
PS. String - не строка, а класс в Wiring
М... +1?
А зачем постоянно висящий в памяти bufArray? На данный момент я решил задачу через malloc (хотя по факту память занята до следующего вызова функции), но хочется все же разобраться с передачей/возвратом char/string.
О, и вопрос к sadman41 - тут тоже размер буфера 3, но все работает...
Fuck, при размере буфера +1 заработало (в первом приближении). Завтра буду еще проверять. Но почему при использовании malloc и размере буфера 3 все работает?
Случайное совпадение: память ещё не была ранее засрана и в позиции +1 находился 0x00, он же \0, он же Z-терминатор строки.
Немного иное распределение памяти при компиляции, работа чуть подольше - в позиции +1 может оказаться что угодно и функция, которая ищет его для определения конца строки, будет искать по всей памяти, до второго пришествия.
С маллоками я бы не советовал вам пока связываться. До тех пор, пока не поймёте как распределяется память под переменную и как работают функции с памятью.
А если 10 раз функцию вызвать - где-то в памяти будет висеть 10 вариантов bufArray?
Будет висеть один экземпляр на 20 байт.
Но если такая функция, например, попадет под template - это будет гульба на все деньги.
Гульба будет, когда незнающий программист попробует вызвать функцию 10 раз, возвращая результаты в разные переменные, и вместо общепринятого поведения функции получит 10 копий одного и того же. Конечно это не очень сильно относится к самописному коду, но писать лучше по правилам. А по правилам либо передают буфер как аргумент, либо возвращают String.
Так 10 вариантов или 10 копий одного и того же (последнего) варианта?
Возьмите и проверьте. Но скорее всего одна копия.Код во флеш, статические данные в ОЗУ. А где реально все лежит определит компилятор. То есть во время компиляции оперативка уйдет на это безобразие.
Да я не на статик акцентировал внимание, а на то что ТС каждый байт пытается сэкономить, а сам String использует направо и налево. Ну пусть глобальный буфер сделает или локальный из вызывающей функции, Sadman ведь уже советовал. В этом случае можно даж указатель не возвращать на буфер. А просто фунция void обновляет буфер и все.
MaksVV,ТС даже String не может пользоваться. Хочет экономить память, то пусть вернет String правой ссылкой.Распечатает и освободит память из кучи с помощью скобки.} Вот только что бы это понять надо получить базовые знания. А если их нет, то объянять долго и бесполезно.
Объясни , если не трудно, что это означает
Гульба будет, когда незнающий программист попробует вызвать функцию 10 раз, возвращая результаты в разные переменные, и вместо общепринятого поведения функции получит 10 копий одного и того же. Конечно это не очень сильно относится к самописному коду, но писать лучше по правилам. А по правилам либо передают буфер как аргумент, либо возвращают String.
я вообще не понял к чему это. У нас результат указатель на массив. Вызвали эту функцию, получили указатель, в данном случае распечатали массив по указателю на лсд. Каждый вызов сразу используем, какие еще копии. Или я чего то не понимаю. Но то что лучше так не делать это я уже понял, а вот почему, еще не очень
Ну дам решение.И что это спасет ТС. Скорее всего нет
Объясни , если не трудно, что это означает
Без примера будет сложновато, а примеры с мобилы не понатыкиваешь.
Есть такая рутина - EEPROM.put(). Принимает на вход переменные стандартных типов и структуры нестандартные. Внутри себя делает цикл от 0 до sizeof() параметра и побайтово сторит. Но как она управляется с любым типом - ведь универсального нет? А все просто - нет функции EEPROM.put(float) изначально. Есть шаблон, из которого компилятор сгенерирует функцию для float, если таковой вызов будет замечен в исходнике. И для int создаст. И для myStruct. Каждому типу - отдельная функция. Типы byte array[5] и byte array[6] тоже разные, к примеру.
В итоге имеем штук 20 практически одинаковых функций. И тут мы решили, что нам срочно нужен static char[20] внутри . Ну, как буфер. Суем объявление в темплейт, компилятор размножает функции и хренак - мы одним движением минусанули 400байт, которые не освободить, ни реюзнуть не можем - область видимости, однако, мешает.
Набили, короче, багажник воздушными шарами и ездим.
Но то что лучше так не делать это я уже понял, а вот почему, еще не очень
Решил ты напечатать три месяца рождения - свой, жены и мамы. Но не просто, а весело - за один прием. Типа "Декапрелеюнь". Сделал три запроса в функцию, каждый результат в свою переменную char * поместил... Думаешь, что в первой будет "Декабрь", во второй "Апрель", в третьей "Июнь"?
пасиб , Про шаблоны не знал. Почитаю. Ппц когда уже закончится период времени, когда я регулярно узнаю то , что я даже не слышал в этом языке. я уж не говорю про разобраться в этом новом...
Нет конечно. Цель была при каждом вызове сразу печатать. Типа так
Del
Ну так это ты будешь делать, если помнишь, что результат работы функции нужно юзать сразу. А через год просто закопипастишь все в другой скетч, строк этак на 20 000 и будешь ловить веселые глюки, пытаясь понять почему в трёх переменных одно и то же.
Поэтому от этого лучше отвыкать не привыкнув.
Ок, намотал
По факту язык Си/Си+ так же далек от программиста, как и от камня. Просто это удобный инструмент для описания сложных механизмов не менее сложными программами.
Возможно не все так однозначно: сидел мучал сегодня скетч, обратил внимание что если размер буфера задаю равным количеству символов, но строку 80 скетча (из поста 19) излагаю в следующей редакции:
(то есть убираю лишние преобразования в String), то скетч работает без глюков, причем довольно долго (сколько у меня терпения хватает). Вариантов два - либо опять случайность, либо мне срочно нужно почитать про работу String (собственно чем и занимаюсь последние сутки).
Ну это я сгоряча сделал, метод тыка в поисках рабочего решения :)
Да я не на статик акцентировал внимание, а на то что ТС каждый байт пытается сэкономить, а сам String использует направо и налево. Ну пусть глобальный буфер сделает или локальный из вызывающей функции, Sadman ведь уже советовал. В этом случае можно даж указатель не возвращать на буфер. А просто фунция void обновляет буфер и все.
ЦЕЛЬ экономии каждого байта не стоит, но стараюсь (по возможности, и в меру своих знаний) к памяти относиться бережно (а то и блинком можно всю память занять при желании). String использую потому что:
а) мне это более менее знакомо
б) не хочется чтобы глобальный буфер постоянно висел в памяти (да, возможно это бессмысленная экономия), при этом чтобы работа с функцией была проще (я привел пример в предыдущем сообщении, продублирую):
т.е. идея такова: вызываю функцию в которой формирую строку для вывода на экран (при этом вызвав из нее функции преобразования числовых значений в текстовые), отправить строку на экран и покинуть функцию. Соответственно память, занятая под преобразование числового значения в текст освободится в тот момент, когда выйдем из функции преобразования, а память занятая в функции вывода на экран освободится по выходу из этой функции. По крайней мере я так представляю работу того, что я понаписал в скетче (сообщение 19). Если я ошибаюсь - прошу меня поправить в рассуждениях.
Безусловно, идея с объявлением буфера и передачей ссылки на него в функцию - классная (положил в копилку знаний), но в данном конкретном случае она (на мой поверхностный взгляд) плохо увязывается с простотой использования вызываемой функции.
ТС даже String не может пользоваться. Хочет экономить память, то пусть вернет String правой ссылкой. Распечатает и освободит память из кучи с помощью скобки.} Вот только что бы это понять надо получить базовые знания. А если их нет, то объянять долго и бесполезно.
Я честно пытаюсь, пытаюсь понять умных человеков. Проблема, как Вы верно заметили, в отсутствии у меня базовых знаний. Вы объясняете элементарные, на Ваш взгляд, вещи исходя из своего уровня, а я со своего уровня из воспринимаю их как татарскую грамоту. Я был бы признателен, если бы ткнули меня носом туда, где можно почерпнуть знания, необходимые для понимания написанного Вами в этой ветке. Все что вижу непонятного сразу пытаюсь загуглить как оно работает, но не всегда доходит с первого раза. Да и со второго тоже.
P.S. Что есть "правая ссылка"?
P.P.S. про фокус со "скобками" для освобождения памяти я в курсе, гляньте пожалуйста скетч в сообщении 19, мне кажется что выделив работу со стрингами в отдельные функции printTime() и getMonthShort() я как раз и добился этого эффекта.
Спасибо за пример, усиленно сижу и пытаюсь разобраться что такое const __FlashStringHelper и с чем его нужно злоупотреблять. Пока не понял :(