Что такое форматированный вывод
Форматированный вывод. Функция printf
Пожалуйста, приостановите работу AdBlock на этом сайте.
Общий синтаксис функции printf следующий:
Рис.1. Общий синтаксис функции printf.
Напоминаю, что параметрами называется то, что мы записываем рядом с именем функции в круглых скобках.
Кроме обязательной строки форматирования есть и необязательные параметры. Они пишутся через запятую после формат-строки.
Формат-строка.
Еscape-последовательности
Часто используемые escape-последовательности:
Как видите, последние четыре последовательности нужны лишь для того, чтобы вывести на экран символы «»», «’», «\» и «?». Дело в том, что если эти символы просто записать в формат-строку, то они не отобразятся на экране, а в некоторых случаях программа и вовсе не скомпилируется.
Следующая программа иллюстрирует работу escape-последовательностей.
Хотя escape-последовательности состоят из нескольких символов, но в потоке вывода они воспринимаются как цельный символ, который имеет своё собственное значение.
Итого, используя управляющие последовательности мы можем влиять на то, как данные будут выводиться на экране.
Спецификаторы формата.
Для каждого типа данных есть свой спецификатор формата. Ниже записаны основные из них.
Основные спецификаторы формата:
Есть и другие спецификаторы формата. Мы познакомимся с ними тогда, когда они нам понадобятся.
Сами спецификаторы формата на экран не выводятся. Вместо них выводятся данные, которые передаются в функцию printf после строки форматирования.
Функция printf работает следующим образом. Все символы, заключенные в двойные кавычки, кроме управляющих последовательностей и спецификаторов формата, выводятся на экран. Спецификаторы формата во время вывода заменяются на значения, указанные после формат-строки. Причем, если используется несколько спецификаторов формата, то первый спецификатор заменяется на первое значение, расположенное после формат строки, второй – на второе, и т.д.
Посмотрим на примерах.
Рис.2 Вывод Листинг 2.
Рис.3 Вывод Листинг 3.
Рис.4 Вывод Листинг 4.
Рис.5 Вывод Листинг 5.
Рис.6 Принцип работы функции printf.
По сути, формат строка задаёт некоторый трафарет(шаблон), в который подставляются данные для вывода, в том порядке, в котором они указаны.
Напишем небольшую программу, которая иллюстрирует использование спецификаторов формата.
Модификаторы формата.
Модификаторы формата записываются между символом % и буквой используемого типа. На рисунке ниже представлена спецификатор формата с использованием модификатора формата.
Рис.7 Модификатор формата
Первое число обозначает ширину поля, выделяемого для записи числа. Второе число обозначает точность, с которой мы хотим вывести данное вещественное число.
В примере на картинке под вещественное число мы выделяем 8 символов и хотим видеть 3 знака после запятой.
Если указанного в ширине количества позиций нам не хватает для вывода числа, то ширина поля увеличивается автоматически, до минимально-возможного количества позиций.
Для иллюстрации описанных возможностей модификаторов формата, напишем небольшую программу.
Результат работы данной программы представлен на рисунке ниже.
Рис.8 Вывод Листинг 8.
Я специально поставил вокруг каждого числа прямые черточки, что можно было увидеть, что означает ширина поля для вывода и как работает выравнивание по левому краю.
Да, чуть не забыл. Мы решили одну из задач, которая стояла перед нами в начале урока.
Практика
Решите предложенные задачи. Для удобства работы сразу переходите в полноэкранный режим
Исследовательские задачи для хакеров:
Дополнительные материалы
Оставить комментарий
Чтобы код красиво отображался на странице заключайте его в теги [code] здесь писать код [/code]
Комментарии
В степике компилятор настроен так, что всем неинициализированным переменным устанавливает значение 0.
На такое поведение надеяться не стоит. В общем случае в переменной будет не нуль, а непонятно что (мусор).
Размещу естественным путем (то есть вручную) на общетематических, форумах уникальные сообщения (посты) с ключевым словом и ссылкой на Ваш сайт. Размером от 200 знаков.
Даю 30 дней гарантии. Если в течение этого времени будет удалена какая либо ссылка, я её заменю.
Ссылки будут размещаться на проверенных форумах в наиболее подходящих разделах общетематических форумов.
Что дает такое размещение?
— Рост позиций вашего сайта в Google по ключевому слову.
— Вычные ссылки
— Дополнительные тематические источники трафика
— Разбавление ссылочной массы
— Тематический трафик на Ваш сайт
Заказав тариф «БИЗНЕС», Вы уже через месяц увидете результат.
Это может быть как повышения трафика, повышение позиций Вашего сайта в Google, увелечение продаж и так далее.
Заказать и ознакомиться с тарифами Вы сможете перейдя по ссылке: (Ссылка ведет на биржу kwork)
Для примера:
— Ссылки с 60 форумов Украина. Создание топиков. http://gg.gg/ukr_links2
— Ссылки с 60 форумов Франции. Создание топиков. http://gg.gg/french-links
— Ссылки с 60 форумов Германии. Создание топиков. http://gg.gg/germany-links
Обращайтесь по контактам: Telegram @eTraffik
I will post in a natural way (that is, manually) on general forums, unique messages (posts) with a keyword and a link to your site. Size from 200 characters.
I give a 30-day guarantee. If any link is removed during this time, I will replace it.
Links will be posted on verified forums in the most appropriate sections of general forums.
What does this placement give?
— Growth of positions
Форматированный ввод и вывод
Форматированный вывод
Сегодня мы рассмотрим две важные функции форматированного ввода и вывода. Устройство и работу этих функций полностью можно понять только после изучения работы с указателями и функций с переменным числом параметров. Но пользоваться этими функциями необходимо уже сейчас, так что некоторые моменты придётся пропустить.
Функция проходит по строке и заменяет первое вхождение % на первый аргумент, второе вхождение % на второй аргумент и т.д. Далее мы будем просто рассматривать список флагов и примеры использования.
Общий синтаксис спецификатора формата
%[флаги][ширина][.точность][длина]спецификатор
Спецификатор – это самый важный компонент. Он определяет тип переменной и способ её вывода.
Спецификатор | Что хотим вывести | Пример |
---|---|---|
d или i | Целое со знаком в в десятичном виде | 392 |
u | Целое без знака в десятичном виде | 7235 |
o | Беззнаковое в восьмеричном виде | 657 |
x | Беззнаковое целое в шестнадцатеричном виде | 7fa |
X | Беззнаковое целое в шестнадцатеричном виде, верхний регистр | 7FA |
f или F | Число с плавающей точкой | 3.4563745 |
e | Экспоненциальная форма для числа с плавающей точкой | 3.1234e+3 |
E | Экспоненциальная форма для числа с плавающей точкой, верхний регистр | 3.1234E+3 |
g | Кратчайшее из представлений форматов f и e | 3.12 |
G | Кратчайшее из представлений форматов F и E | 3.12 |
a | Шестнадцатеричное представление числа с плавающей точкой | -0xc.90fep-2 |
A | Шестнадцатеричное представление числа с плавающей точкой, верхний регистр | -0xc.90FEP-2 |
c | Буква | a |
s | Строка (нуль-терминированный массив букв) | Hello World |
p | Адрес указателя | b8000000 |
n | Ничего не печатает. Аргументом должен быть указатель на signed int. По этому адресу будет сохранено количество букв, которое было выведено до встречи %n | |
% | Два идущих друг за другом процента выводят знак процента | % |
Суб-спецификатор длины изменяет длину типа. В случае, если длина не совпадает с типом, по возможности происходит преобразование до нужного типа.
спецификаторы | |||||||
---|---|---|---|---|---|---|---|
Длина | d, i | u o x X | f F e E g G a A | c | s | p | n |
(none) | int | unsigned int | double | int | char* | void* | int* |
hh | signed char | unsigned char | signed char* | ||||
h | short int | unsigned short int | short int* | ||||
l | long int | unsigned long int | wint_t | wchar_t* | long int* | ||
ll | long long int | unsigned long long int | long long int* | ||||
j | intmax_t | uintmax_t | intmax_t* | ||||
z | size_t | size_t | size_t* | ||||
t | ptrdiff_t | ptrdiff_t | ptrdiff_t* | ||||
L | long double |
Форматированный ввод
Как и в printf, ширина, заданная символом * ожидает аргумента, который будт задавать ширину. Флаг длина совпадает с таким флагом функции printf.
Кроме функций scanf и printf есть ещё ряд функций, которые позволяют получать вводимые данные
Это не полный набор различных функций символьного ввода и вывода. Таких функций море, но очень многие из них небезопасны, поэтому перед использованием внимательно читайте документацию.
Непечатные символы
В си определён ряд символов, которые не выводятся на печать, но позволяют производить форматирование вывода. Эти символы можно задавать в виде численных значений, либо в виде эскейп-последовательностей: символа, экранированного обратным слешем.
Форматированный вывод
Вы будете перенаправлены на Автор24
Форматированный вывод — это использование специальной функции printf, которая выполняет форматированный вывод информации.
Форматированный вывод: сущность понятия
Под форматированным выводом понимается применение функционального набора или методик общеизвестных библиотек отдельных программных языков, используемых для вывода разных информационных значений, прошедших форматирование по некоторому шаблону.
Такой шаблон задаётся форматной строкой, которая сформирована по определённым законам. Самой известной функцией этого класса, считается функция printf и некоторые её модификации в базовой библиотеке языка Си. Они так же входят в состав основной библиотеки С++ и Objective-C. Различные версии ОС UNIX тоже имеют в своём наборе команду printf, которая также выполняет процедуру форматированного вывода. Аналогом этой функции в старых языках программирования, подобных Фортрану, считается оператор FORMAT. В языках, предках языка Си (это BCPL и B), тоже была функция вывода, которая управлялась строкой. Но только в базовой библиотеке языка Си эта функция приобрела полноценный формат и сопутствующие параметры. Синтаксический набор строки шаблона вывода, которую часто называют строкой форматирования, с течением времени перешёл и в другие программные языки. Обычно, функции вывода этих языков тоже обозначают как printf с различными вариациями и модификациями от этой команды. Отдельные более современные языки программирования, такие как, к примеру, NET, тоже применяют методику вывода, которая управляется строкой форматирования, но используют другой синтаксический набор.
История развития форматированного вывода
Как отмечалось выше, уже Фортран обладал операторами, которые могли выполнить форматированный вывод. В синтаксическом наборе операторов WRITE и PRINT была предусмотрена метка, которая отсылала к оператору FORMAT, который считался неисполняемым и обладал спецификацией формата. Синтаксис спецификации входил в синтаксис оператора, что позволяло компилятору тут же формировать коды, которые фактически выполняют форматирование информации. Это оптимизировало уровень производительности на электронных вычислительных машинах того времени. Но были и заметные недостатки:
Изначальный прототип команды printf, которым считается функция WRITEF, возник в программном языке BCPL в шестидесятых годах прошлого века. Эта функция воспринимает форматную строку, где тип данных подаётся отдельно от непосредственно данных в строковой переменной. Главной задачей разработки форматной строки считалась возможность передачи типов аргументов. Функция WRITEF позволяла упростить вывод данных, так как заменяла сразу целый ряд команд, таких как WRCH (позволяет выводить символ), WRITES (выводит строку), и ещё несколько функций, задающих числовой вывод в разных форматах. То есть применялся один вызов, в котором чередовались текстовые данные с выходными параметрами. В 1969-ом году был разработан язык Би, который стал применять символику printf вместе с простой форматной строчкой, где можно было указать один из трёх типов данных и числа в двух допустимых форматах.
Готовые работы на аналогичную тему
Но когда появился язык Си, в начале семидесятых годов прошлого века, набор функций printf становится главным способом форматированного вывода. Потери времени на расшифровку данных строки форматирования во время каждого обращения к функции разработчики сочли допустимыми, и никакие альтернативы для любого отдельного типа не вошли в стандартную библиотеку. В модификацию языка Си, получившую название C++, была включена последняя версия стандартной библиотеки, включая и весь набор printf. Но при этом появились и альтернативы, которые в стандартной библиотеке C++ назывались набором классов потокового вывода, а также ввода. Операторы вывода этого класса безопасны в плане порчи типа и для них не требуется разбор строк форматирования при выполнении каждого вызова. Но некоторые специалисты по-прежнему используют набор функций printf, поскольку они получаются в более компактной записи при последовательном выводе и смысл используемого формата понятнее.
Язык Objective-C считается утончённой модификацией Си, и при написании программ на этом языке возможно прямо использовать всё семейство функций printf. Кроме языка Си и его модификаций, функции форматирования вывода с синтаксическим набором форматной строки, подобным printf, применяют различные другие программные языки. Например:
Форматный вывод на Си для микроконтроллеров.
Форматированный ввод-вывод применяется очень широко, в первую очередь это, конечно, взаимодействие с пользователем, а так-же отладочный вывод, логи, работа с различными текстовыми протоколами и многое другое. В этой статье рассматриваются особенности применения форматированного вывода (ввод оставим на потом) для микроконтроллеров.
Первая программа написанная для ПК традиционно называется «Hello, world» и естественно пишет в стандартный вывод эту знаменитую фразу:
Первая программа для микроконтроллера обычно зовётся «Blinky» и она просто мигает светодиодом. Дело в том, что заставить работать традиционный «Hello, world» на микроконтроллере не так уж и просто для начинающего. Во первых, нет стандартного устройства вывода и его функциональность ещё нужно реализовать. Во вторых, не всегда бывает очевидно как подружить стандартные функции вывода с целевым устройством. Ситуация усугубляется тем, что в каждом компиляторе (тулчейне) это делается каким-то своим способом.
Форматный вывод.
Что-же, в общем, за зверь такой форматный вывод? Упрощенно говоря, это — вывод значений различных типов в виде текстовых полей.
Текстовое поле состоит из собственно значения, преобразованного в строку символов, и заполняющих символов для получения нужной ширины поля. Заполняющие символы могут находится слева или справа от значения, с помощью этого получается выравнивание по правому или левому краю соответственно. Заполнение также может находится внутри значения между определёнными его элементами, например между знаком и числом (как на рисунке), или между базой шестнадцатеричного числа (0x) и числом.
В качестве заполняющего символа обычно используется пробел, однако иногда могут использоваться нули, например в качестве ведущих нулей, или ещё что-нибудь.
Таким образом, форматированный вывод это — преобразование значений различных типов в текстовую форму и вывод их с определённым выравниванием и определённым заполнением.
Требования и особенности ввода-вывода для МК.
1. Гибкость. В отличии от старших братьев, для МК нет и не может быть стандартного устройства ввода-вывода. В каждой системе будет что-то своё, с уникальными требованиями и особенностями. В одной системе будет вывод в USART, во второй — на ЖК дисплей, в третей — в файл на SD карточке, в четвёртой — всё сразу. Значит система форматированного ввода-вывода должна быть достаточно гибкой, настраиваемой и независимой от аппаратных особенностей целевой платформы.
2. Требования к ресурсам. В МК ресурсов бывает мало и очень мало. В идеале, в прошивку МК должна попасть только та функциональность, которая реально используется. Скорость и размер кода имеют значение, под час решающее. Если мы не используем вывод типов с плавающей точкой, то и в полученной прошивке не должно быть кода, который его осуществляет.
3. Стойкость к ошибкам кодирования. В идеале, хорошо было-бы если ошибка кодирования приводила бы сразу к ошибке компиляции, ну или по крайней мере к предупреждению. Чтоб не надо было заливать программу в железо и ходить отладчиком вылавливать место, где там, в функцию предался неправильный аргумент.
4. Доступность. Библиотека ввода-вывода должна быть в наличии для целевой платформы.
5. Функциональность часто ставится на последнее место в угоду скорости и компактности.
Стандартная библиотека Си.
Стандартным и единственно доступным средством форматированного вывода в Си является семейство функций: printf, sprintf, snprintf и fprintf.
Рассматривать будем функцию printf, как наиболее типичного и часто используемого представителя функций форматного вывода, при необходимости переходя к другим. Итак фунция printf имеет следующий синтаксис:
Это обычная функция с переменным числом параметров. Здесь первый аргумент fmt является форматной строкой, которая содержит, как обычный текст, так и специальные управляющие последовательности. Троеточие (. ) обозначает список дополнительных параметров, их может быть от нуля и до сколько поместится в стеке. Да, да все параметры в функцию printf передаются преимущественно в стеке (за исключением архитектуры ARM, где первые 4 параметра передаются в регистрах). Запихивает их в стек вызывающая функция, она-же инициализирует кадр стека перед вызовом и очищает после. Сама printf узнать сколько и каких параметров ей было передано может узнать только из форматной строки. При передачи параметров размером меньше (signed char, unsigned char, char и на некоторых платформах signed/unsigned short), чем int, они будут расширенны соответствующим расширением(знаковым или без-знаковым) до int-a. Это сделано для того, чтобы стек был всегда выровнен по границе машинного слова, а так-же уменьшает количество возможных ошибок при нестыковке размера фактического параметра и ожидаемого из форматной строки. Так-же параметры типа float при передаче в функции с переменным числом аргументов, приводятся к типу double.
Форматная строка содержит как символы непосредственно выводимые в поток, так и специальные управляющие последовательности, которые начинаются со знака «%». Управляющие последовательности имеют следующий формат:
Единственным обязательным элементом здесь является спецификатор, который определяет интерпретацию типа соответствующего параметра и может принимать следующие значения:
Флаги определяют дополнительные параметры форматирования (их может быть несколько):
Ширина — десятичное число, задаёт минимальное количество символов выводимых для соответствующего параметра. Если выводимое значение содержит меньше символов, чем указано, то оно будет дополнено пробелами (или нулями если есть флаг «0») до нужной ширины слева или справа, если указан флаг «-«. Например:
Если вместо десятичного числа указать «*», то значение ширины будет считанно из дополнительного целочисленного параметра. Это позволяет задавать значение ширины поля вывода из переменной:
Здесь первый раз i передаётся в качестве ширины поля, второй — значения.
Точность или длинна — десятичное число после знака точки «.». В случае вывода целых чисел этот элемент означает минимальное количество записанных знаков, если выводимое число короче указанной длинны, то оно дополняется ведущими нулями, если число длиннее, то оно не урезается.
Таким образом есть уже два способа вывести целое с нужным количеством ведущих нулей.
Для чисел с плавающей точкой в форматах «e», «E» и «f» этот элемент означает число знаков после десятичной точки. Результат округляется.
Для форматов «g» и «G» это — общее количество выведенных значимых цифр.
Для строк «s» этот элемент называется длинна и означает максимальное количество выведенных символов, обычно строки выводятся пока не встретится нулевой завершающий символ.
Также как и в элемента «ширина», в место точности можно поставить звёздочку и передать её значение в виде дополнительного целочисленного параметра:
Дополнительный модификатор служит для указания размерности типа:
h — применяется со спецификаторами i, d, o, u, x и X для знаковых и без-знаковых коротких целых short и unsigned short).
l — совместно со спецификаторами i, d, o, u, x и X означают длинные целые long и unsigned long).
l — совместно со спецификаторами s и c «широкие» многобайтные строку и символ соответственно.
L — обозначает тип long double, применяется со спецификаторами e, E, f, g и G.
В компиляторах поддерживающих тип long long, таких как GCC и IAR, часто есть для него нестандартный модификатор ll.
В стандарте С99 добавлены модификаторы «t» и «Z» для типов ptrdiff_t и size_t соответственно.
Работа над ошибками
Основным недостатком функций семейства printf считается вовсе не громоздкость и неторопливость — размер кода и накладные расходы на запихивание параметров в стек и разбор форматной строки обычно считаются приемлемыми, а подверженность ошибкам кодирования. Самое неприятное в этих ошибках то, что они могут быть неочевидными и проявляться не всегда, или не на всех платформах.
Большинство ошибок связано с несоответствием спецификаторов указанных в форматной строке с количеством и типами фактических аргументов. При этом можно выделить следующие ситуации:
— занимаемый в стеке размер параметров ожидаемых из форматной строки меньше размера фактически переданных. Типы фактические параметров совпадают с ожидаемыми. В этом случае просто выведутся параметры указанные в форматной строке, а лишние будут проигнорированы.
— занимаемый в стеке размер параметров ожидаемых из форматной строки меньше размера фактически переданных. Типы фактические параметров не совпадают с ожидаемыми. В этом случае параметры просто будут интерпретированы в соответствии с форматной строкой и в общем случае будет выведен мусор.
— размер фактических параметров меньше ожидаемых. Здесь поведение не определено и зависит от кучи разных факторов — от платформы, от компилятора, от содержимого стека на момент вызова printf. Поведение printf при этом может быть от внешне корректной работы и до чтения и даже записи произвольных участков памяти со всеми вытекающими последствиями.
Многие ошибки возникают при переносе кода с одной платформы на другую у которых отличаются размеры типов. Например:
На платформах, где int имеет 32 бита этот код работает правильно, а где int — 16 бит — будут выведены только 2 младших или старших (в зависимости от порядка следования байт) байта.
К счастью некоторые компиляторы, например GCC, знают printf «в лицо» и выдают предупреждения в случае несовпадения фактических параметров с форматной строкой. Однако это работает только если форматная строка задана литералом. А если она хранится в переменной (например extern указатель инициализируемый в другом модуле), то компилятор бессилен и проследить соответствеи параметров может быто очень не просто.
Особенности реализаций
AVR-GCC он-же WinAVR
В AVR-GCC, а точнее в avr-libc самая удачная, на мой взгляд, реализация стандартной библиотеки ввода-вывода Си. В ней имеется возможность выбирать необходимый функционал функций семейства printf. Они прекрасно работают со строками как RAM так и во Flash. Все функции семейства, включая snprintf и fprintf разделяют общий код и очень хорошо оптимизированы как по скорости, таки по объёму кода.
Для поддержки находящихся во Flash памяти строк введен новый спецификатор форматной строки %S — S — заглавная, строчная s по-прежнему означает строку в RAM. Но во Flash памяти может быть и сама форматная строка, для этого есть специальные модификации функций с суффиксом «_P» printf_P, fprintf_P, sprintf_P и т. д., которые ожидают форматную строку во Flash.
Для того чтобы printf заработала, нужно написать функцию вывода символа и определить файловый дескриптор стандартного вывода.
Помимо stdout есть стандартные дескрипторы stdin и stderr для стандартного ввода и стандартного вывода ошибок соответственно. Используя функцию fprintf можно явно указывать нужный файловый дескриптор и при необходимости можно определить сколько угодно устройств вывода:
В avr-libc имеется три уровня функциональности библиотеки ввода-вывода:
1. нормальный — поддерживается вся упомянутая выше функциональность, кроме вывода чисел с плавающей точкой. Этот режим включен по умолчанию и каких либо опций для него указывать не надо. Поддержка чисел с плавающей точкой занимает много места, а нужна сравнительно редко. Приведенный выше пример, скомпилированный с этим уровнем функциональности, занимает порядка 1946 байт Flash памяти.
2. минимальный — поддерживаются только самые базовые вещи: целые, строки, символы. Флаги, ширина поля и точность, если они есть в форматной строке, разбираются корректно, но игнорируются, поддерживается только флаг «#». Пример, скомпилированный с этим уровнем функциональности, занимает порядка 1568 байт Flash памяти. Его вполне можно было-бы применить на контроллере с 2 Кб flash памяти. Включается указанием в командной строке компоновщика («Linker options» в AVRStudio, а не компилятора, как расплывчато указано в документации avr-libc) следующих опций:
3. максимальный — полная функциональность, включая поддержку чисел с плавающей точкой. Включается опциями
Скомпилированный пример занимает при этом 3488 байт.
Функции семейства printf из avr-libc не используют буферизацию, не считая буферов для конвертации чисел, и выводят символы в устройство по мере их обработки. Поэтому, если буферизация нужна, то ее можно реализовать самостоятельно в функции вывода символа, там можно реализовать и кольцевой буфер и все, что угодно. Также в этих функциях не используется динамическая память, что в нашем случае очень хорошо, зато активно используется стек. Попробуем определить максимальное использование стека в них. Для этого в приложенном архиве заходим в каталог AvrGccPrintf, компилируем проект посредством AVRStudio и запускаем симуляцию с помощью runSimul.cmd.
После открывает образовавшийся файл trace.log, находим значение указателя стека (SP) после входа в main (после пролога), находим минимальное значение SP (стек растёт вниз) и вычитаем из первого второе. У меня получилось 0x455 — 0x429 = 0x2c = 44 байта использует сама функция fprintf, плюс еще 8 байт в стеке занимают ее параметры, итого 52 байта. Еще 14 байт занимает один файловый дескриптор и ещё 6 байт три стандартных указателя на файловые дескрипторы (stdout, stdin, stderr). Итого 72 байта RAM только на вызов fprintf, без учета всего остального.
Также из файла trace.log можно узнать общее время выполнения функций и где процессор проводит его больше всего.
Подробнее о стандартной библиотеке ввода-вывода avr-libc можно здесь:
www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html
IAR for AVR
Здесь есть несколько версий Си-шных библиотек, с отличающейся функциональностью:
— CLIB — относительно маленькая, но ограниченная библиотека. Нет поддержки файловых дескрипторов, локалей и многобайтных символов. Считается устаревшей.
— Normal DLIB — более новая. Так-же нет поддержки файловых дескрипторов, локалей и многобайтных символов, но есть некоторые плюшки из стандарта С99.
— Full DLIB — полная библиотека Си. Поддерживает всё согласно стандарту С99, но при этом очень объёмная. Рассматривать этот вариант не буду, так, как размер функции printf отсюда превышает доступные 4 Кб кода для бесплатной версии IAR KickStart for AVR.
В IAR имеется возможность выбирать возможности для printf. Для этого заходим с меню Project->Options далее в диалоге General Options->Library Configuration. В списке «Printf formatter» можно выбрать необходимый уровень функционала. Для CLIB это Large, Small, Tiny, для DLIB добавляется еще Full. По возможностям эти уровни примерно соответствуют аналогичным в avr-gcc, поэтому расписывать их не буду.
Для того чтобы printf заработала, надо определить функцию вывода символа:
Разберём полный пример. Он также предназначен для запуска на симуляторе.
Тут выясняются две неприятные особенности. Во-первых функции printf и printf_P видимо не разделяют общий код и printf_P всегда использует максимальный форматтер, независимо от того, что выбрали в настройках для printf, занимая порядка 3500 байт кода. Поэтому приведенный пример не помещается в четыре бесплатных килобайта. Для проверки одну из функций надо закомментировать.
Во-вторых, ни printf, ни printf_P не умеют читать строки из flash памяти — для них нет спецификатора.
Размеры printf для различных уровней функциональности примерно соответствуют аналогичным у avr-gcc, где чуть меньше, где чуть больше — непринципиально. А вот использование стека в разы выше, минимальный размер стека данных, при котором printf заработала, составил 200 байт для DLIB и около 150 для CLIB. Так, что на контроллерах с 2 кб flash, 128 RAM использовать эти функции не получится.
Демо-проект находится в каталоге AvrIarPrintf. Для запуска симуляции, а точнее преобразования генерируемого IAR-ом hex-а в пригодный для потребления simulavr-ом elf, на машине должен быть установлен WinAVR (и прописан в переменной окружения PATH, естественно).
Mspgcc
В стандартной библиотеке mspgcc для форматного вывода реализованы только функции printf и sprintf, плюс еще нестандартная функция uprintf, которая принимает указатель на функцию вывода символа в качестве первого аргумента. Файловых дескрипторов там нет и в помине, выбирать уровень функциональности форматтеров тоже нельзя. При этом printf «весит» порядка двух килобайт так, что запустить её, скажем, на MSP43 Launchpad-е не получится.
Для работы printf нужно, вполне ожидаемо, определить функцию int putchar(int c):
IAR for MSP430, IAR for ARM и может еще какой IAR
Вообще в реализациях стандартной библиотеки от IAR Systems всё довольно однообразно для различных платформ, что не может не радовать. Как правило есть минимум две версии стандартной библиотеки — одна урезанная без поддержки файловых дескрипторов, вторая — полная, соответственно, с их поддержкой. Зовутся они как правило «Normal» и «Full» соответственно. Так-же в каждой из них можно выбирать необходимый функционал разбора форматной строки, поддерживаемый функциями семейства printf. Варианты уже уже описаны для IARfor AVR: Large, Small, Tiny и Full.
Если выбрать вариант библиотеки без файловых дескрипторов, то для работы функций вывода нужно определить лишь функцию int putchar(int outchar).
Если используем вариант с дескрипторами, то определить нужно функции __write, __lseek, __close и remove.
Минимальная их реализация, например, для STM32 может выглядеть так:
ARM + NewLib
Большинство сборок GCC под ARM используют NewLib в качестве стандартной библиотеки Си. Это достаточно взрослый проект и хорошо соответствует Си-шным стандартам, но при этом она относительно «тяжела» и требовательна к ресурсам. Для настройки библиотеки под свои нужды используется механизм системных вызовов. О нём уже немного писалось тут ispolzuem-libc-newlib-dlya-stm32, поэтому подробно на них останавливаться не буду, а упомяну об особенностях.
Первое это требования к памяти. Все stdio функции из NewLib используют хитрую буферизацию и динамически распределяют память для своих внутренних нужд. А значит им нужна куча и не маленькая, а в целых 3 Кбайт. Плюс примерно 1500 байт на статические структуры и плюс около 500 байт стека. Итого только чтоб напечатать «Hello, world!» нужно порядка 5 Кб оперативки. Что как-бы чуть больше, чем много для STM32-Discovery, на которой я запускал тестовый пример, с её 8 килобайтами. Также при использовании таких функций как printf по зависимостям тянется практически вся stdio плюс функции поддержки плавающей точки. В итоге тестовая прошивка занимает чуть меньше 30 Кб памяти программ. Если отказаться от использования чисел с плавающей точкой, то вместо printf можно использовать её облегченный аналог iprintf. В этом случае объём тестовой прошивки будет около 12 Кб.
Если какая либо из функций stdio не сможет выделить необходимую её память из кучи, то она тихонечко свалится в HardFault без объяснений причин.
Еще один момент это буферизация. Она может несколько запутать. Вызываем:
И… Ничего не происходит. Хотя системные вызовы правильно определены, куча настроена, места в ней хватает.
Наш вывод был спокойно положен в буфер и ждет там либо пока буфер не заполнится, либо не будет принудительно сброшен. Сбросить этот буфер можно с помощью функции fflush, или послав на вывод символ новой строки.
Режимом буферизации можно управлять с помощью функции setvbuf. Например, чтоб отключить буферизацию нужно сделать такой вызов до того, как в целевой поток был произведен какой либо вывод тыц:
При этом потребление памяти кучи уменьшится более чем на 1.5 Кб.
Xprintf
Это реализация функций похожих на printf от товарища Chan-а. Качаем отсюда:
elm-chan.org/fsw/strf/xprintf.html
Библиотека содержит следующие функции для форматированного вывода — аналоги функций стандартной библиотеки Си:
Все функции написаны целиком на Си и их можно использовать практически на любом микроконтроллере. Однако они не учитывают особенностей некоторых МК, например, нет никакой поддержки строк во Flash памяти для семейства AVR, что не добавляет удобства использования. Для использования xprintf необходимо инийиализировать указатель xfunc_out, присвоив ему адрес функции вывода символа. Рассмотрим пример. Компилятор avr-gcc, проект AvrStudio, рассчитан на запуск в симуляторе simulavr.
Здесь для вывода строк из Flash памяти их приходится предварительно скопировать в оперативку.
Из достоинств этой библиотеки можно выделить компактность кода (
1600 байт для приведённого примера) и лёгкость использования на различных платформах и модифицировать. На этом достоинства заканчиваются. Из недостатков стоит отметить
относительно медленную работу, примерно в полтора-два медленнее чем стандартная printf из avr-libc, и несоответствие стандартам — не поддерживаются некоторые флаги (‘пробел’, ‘ #’, ‘+’ ), спецификаторы (i, p n), не считая флагов для чисел с плавающей точкой и т.д.
Потребление стека порядка 38 байт не считая аргументов.
Описание примеров.
AvrGccPrintf
AvrIarPrintf
AvrXprintf
Msp430GccPrintf
Msp430IarPrintf
Stm32Format
IarArm
Работает на STM32 Discovery. Собирается с помощью IAR for ARM.
Итоги.
Преимущества использования стандартной библиотеки Си для форматного вывода:
— Стандартность. Этим всё сказано.
— Доступность. В каком-то виде есть практически в любом Си компиляторе.
— Разделение формата вывода и выводимых данных. Форматную строку легко вынести в отдельный файл/модуль, например для дальнейшей локализации.
— Хорошая функциональность.
Недостатки:
— Полностью стандартная реализация в большинстве случаев слишком «тяжела» для микроконтроллеров.
— Использование лишь одной функции, например printf, тянет за собой значительную часть библиотеки вывода, даже если реально используются только ограниченные возможности.
— Неаккуратное использование функций с форматной строкой может привести к трудно обнаруживаемым ошибкам кодирования.
— В каждом компиляторе используется какой-то свой способ для определения низкоуровневых функций вывода.
Комментарии ( 39 )
Пример, скомпилированный с этим уровнем функциональности, занимает порядка 1568 байт Flash памяти. Его вполне можно было-бы применить на контроллере с 2 Кб flash
100 байт свободно 🙂
Сам форматный вывод в примерно 1000 с небольшим байт помещается (и для AVR и для MSP430).
Об этом следующая статья будет. С шаблонами и выносом мозга.
Отличная статья – видно, что проделана большая работа.
Позволю себе небольшое дополнение: если говорить о выводе отладочной информации то такая информация как правило нужна только на этапе разработки. В релизном коде часто отладку отключают. Для того чтобы упростить процесс включения/отключения отладки можно применить директивы условной компиляции. Например, такая реализация:
Соответственно в коде для вывода отладки вместо printf используем _DBG, например
Теперь отладка будет выводиться, только если в определен символ _DEBUG, в противном случае вся откатка будет вырезана препроцессором.