Программирование AVR stdio.h: стандартные возможности ввода/вывода Mon, November 20 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


stdio.h: стандартные возможности ввода/вывода Печать
Добавил(а) microsin   

Это руководство поможет Вам разобраться в следующих вопросах использования printf с микроконтроллерами AVR:

1. Как перенаправить вывод printf на экран LCD, в UART, или любое другое пользовательское устройство вывода.

2. Как сконфигурировать стандартную библиотеку ввода/вывода (stdio), чтобы обрабатывались нужные опции форматирования printf (%X, %f и т. п.), или чтобы код обработки printf не занимал лишней памяти.

[Памятка для тех, кто все знает и кому лень читать дальше]

Пример:

#include < stdio.h >
 
int main(void)
{
   printf("Hello, world!\n");
   return 0;
} 

Пока это пустая болванка, которая ничего не умеет. По замыслу нужно, чтобы текст, который генерирует printf, выводился через USART.

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

Опция printf Вариант обработки формата % Описание
-Wl,-u,vfprintf -lprintf_min минимальный Поддерживается только %s, %i, %x, причем с ограничениями (нет к примеру, поддержки выравнивания наподобие %08x и т. п.).
опция не указана по умолчанию (средний) Работает %s, %i, %x.
-Wl,-u,vfprintf -lprintf_flt -lm максимальный Работают все возможные опции, включая %f.

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

#include < stdio.h >
 
static int uart_putchar(char c, FILE *stream)
{
   if (c == '\n')
      uart_putchar('\r', stream);
   loop_until_bit_is_set(UCSRA, UDRE);
   UDR = c;
   return 0;
}
 
int main(void)
{
   init_uart();
   printf("Hello, world!\n");
   return 0;
} 

Библиотека avrlibc имеет встроенное средство для того, чтобы связать Вашу функцию вывода с выходными данными функции printf:

#include < stdio.h >
 
static int uart_putchar(char c, FILE *stream);
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
 
static int uart_putchar(char c, FILE *stream)
{
   if (c == '\n')
      uart_putchar('\r', stream);
   loop_until_bit_is_set(UCSRA, UDRE);
   UDR = c;
   return 0;
}
 
int main(void)
{
   init_uart();
   stdout = &mystdout;
   printf("Hello, world!\n");
   return 0;
} 

[Макросы]

#define stdin (__iob[0])
#define stdout (__iob[1])
#define stderr (__iob[2])
#define EOF (-1)
#define fdev_set_udata(stream, u) do { (stream)->udata = u; } while(0)
#define fdev_get_udata(stream)   ((stream)->udata)
#define fdev_setup_stream(stream, put, get, rwflag)
#define _FDEV_SETUP_READ __SRD
#define _FDEV_SETUP_WRITE __SWR
#define _FDEV_SETUP_RW (__SRD|__SWR)
#define _FDEV_ERR (-1)
#define _FDEV_EOF (-2)
#define FDEV_SETUP_STREAM(put, get, rwflag)
#define fdev_close()
#define putc(__c, __stream) fputc(__c, __stream)
#define putchar(__c) fputc(__c, stdout)
#define getc(__stream) fgetc(__stream)
#define getchar() fgetc(stdin)
#define BUFSIZ 1024 
#define _IONBF 0

[Типы]

typedef struct __file FILE
typedef long long fpos_t

[Функции]

int fclose (FILE *__stream);
int vfprintf (FILE *__stream, const char *__fmt, va_list __ap);
int vfprintf_P (FILE *__stream, const char *__fmt, va_list __ap);
int fputc (int __c, FILE *__stream);
int printf (const char *__fmt,...);
int printf_P (const char *__fmt,...);
int vprintf (const char *__fmt, va_list __ap);
int sprintf (char *__s, const char *__fmt,...);
int sprintf_P (char *__s, const char *__fmt,...);
int snprintf (char *__s, size_t __n, const char *__fmt,...);
int snprintf_P (char *__s, size_t __n, const char *__fmt,...);
int vsprintf (char *__s, const char *__fmt, va_list ap);
int vsprintf_P (char *__s, const char *__fmt, va_list ap);
int vsnprintf (char *__s, size_t __n, const char *__fmt, va_list ap);
int vsnprintf_P (char *__s, size_t __n, const char *__fmt, va_list ap);
int fprintf (FILE *__stream, const char *__fmt,...);
int fprintf_P (FILE *__stream, const char *__fmt,...);
int fputs (const char *__str, FILE *__stream);
int fputs_P (const char *__str, FILE *__stream);
int puts (const char *__str);
int puts_P (const char *__str);
size_t fwrite (const void *__ptr, size_t __size, size_t __nmemb, FILE *__stream);
int fgetc (FILE *__stream);
int ungetc (int __c, FILE *__stream);
char * fgets (char *__str, int __size, FILE *__stream);
char * gets (char *__str);
size_t fread (void *__ptr, size_t __size, size_t __nmemb, FILE *__stream);
void clearerr (FILE *__stream);
int feof (FILE *__stream);
int ferror (FILE *__stream);
int vfscanf (FILE *__stream, const char *__fmt, va_list __ap);
int vfscanf_P (FILE *__stream, const char *__fmt, va_list __ap);
int fscanf (FILE *__stream, const char *__fmt,...);
int fscanf_P (FILE *__stream, const char *__fmt,...);
int scanf (const char *__fmt,...);
int scanf_P (const char *__fmt,...);
int vscanf (const char *__fmt, va_list __ap);
int sscanf (const char *__buf, const char *__fmt,...);
int sscanf_P (const char *__buf, const char *__fmt,...);
int fflush (FILE *stream);
int fgetpos (FILE *stream, fpos_t *pos);
FILE * fopen (const char *path, const char *mode);
FILE * freopen (const char *path, const char *mode, FILE *stream);
FILE * fdopen (int, const char *);
int fseek (FILE *stream, long offset, int whence);
int fsetpos (FILE *stream, fpos_t *pos);
long ftell (FILE *stream);
int fileno (FILE *);
void perror (const char *s);
int remove (const char *pathname);
int rename (const char *oldpath, const char *newpath);
void rewind (FILE *stream);
void setbuf (FILE *stream, char *buf);
int setvbuf (FILE *stream, char *buf, int mode, size_t size);
FILE * tmpfile (void);
char * tmpnam (char *s);
FILE * fdevopen (int(*put)(char, FILE *), int(*get)(FILE *));

[Введение

Файл stdio.h, подключаемый директивой #include, декларирует стандартные возможности ввода/вывода, реализованные в библиотеке avr-libc. Из-за внутреннего устройства нижележащей аппаратуры реализован только ограниченный набор стандартных средств ввода/вывода, которые обычно бывают доступны на "больших" системах. Здесь нет действительной поддержки файлов, есть только ввод/вывод устройства. Из-за того, что операционная система на AVR отсутствует, приложению нужно предоставить некую дополнительную информацию о том, как получить доступ подсистемам ввода/вывода микроконтроллера, чтобы их можно было использовать для стандартного I/O (см. далее макросы для организации стандартных потоков).

Также в среде выполнения AVR есть определенные ограничения по ресурсам (часто недостаточное количество памяти программ FLASH, оперативной памяти SRAM), поэтому некоторый функционал не реализован полностью (наподобие сложных преобразований printf). Тем не менее потенциальным пользователям подсистемы следует знать, что не смотря на ограничения реализации, функции семейства printf и scanf даже связанные с простейшим выводом типа "Hello, world!" в программе, в действительности довольно сложны, и займут довольно много места в памяти кода. Также они небыстрые из-за интерпретации (декодирования) строки формата во время выполнения. Т. е. к примеру, нечего и думать, чтобы использовать printf или scanf в микроконтроллерах типа ATtiny45 или ATmega8. Поэтому всякий раз, где это возможно, используйте другие, более простые, пусть даже не слишком универсальные (и возможно не стандартные, а написанные самостоятельно) подпрограммы преобразования в текст, которые будут потреблять меньше ресурсов (с точки зрения памяти и быстродействия). 

Настраиваемые опции для кода против набора возможностей. Чтобы позволить программисту выбрать какой-то компромисс между функциональностью и затратами места под код, функция vfprintf() (которая является сердцем семейства функций printf) позволяет выбрать между разными вариантами обработки формата, используя опции линкера. Подробное описание см. в документации на функцию vfprintf(). Те же опции прикладываются и к vfscanf(), относящейся к семейству функций scanf. 

Общая схема выбранного API. Предоставлены стандартные потоки stdin, stdout и stderr, но ограниченные стандартом C. Поскольку avr-libc ничего не знает о том, на каком устройстве работает, эти потоки не инициализированы при старте приложения. Кроме того, поскольку нет никакого понятия "file" применительно к avr-libc, то нет никакой функции fopen(), которую можно использовать для привязки к потоку (stream) на некотором устройстве (микроконтроллере), см. примечание 1. Вместо этого предоставлена функция fdevopen(), позволяющая привязать stream к устройству, где для устройства нужно предоставить функцию для отправки символа, приема символа, или и то, и другое. Библиотека avr-libc внутренне не делает никаких различий между текстовыми потоками ("text" stream) и двоичными ("binary" stream). Символ \n отправляется вниз к функции устройства put(). Если устройству нужно отправить символ перевода каретки (carriage return, \r) перед переводом строки (linefeed, \n), то подпрограмма устройства put() должна сама реализовать это (см. также примечание 2). 

Для настройки предоставленной пользователем структуры FILE можно использовать для fdevopen() альтернативный метод, макрос fdev_setup_stream(). 

Следует отметить, что автоматическое преобразование символа новой строки (newline character) в последовательность символов (carriage return - newline) повреждает двоичные передачи. Если нужны двоичные передачи, то не должно быть автоматического преобразования символов, однако при этом любая строка, которая стремится выдать последовательность CR-LF, должна явно использовать "\r\n". 

Для удобства первый вызов fdevopen(), который откроет поток (stream) для чтения, в результате выдаст поток, на который можно ссылаться по алиасу stdin. Аналогично первый вызов fdevopen(), который откроет поток на запись, свяжет его с обоими алиасами stdout и stderr. Также если открытие было сделано и на чтение, и на запись, то будут созданы три стандартных идентичных потока. Имейте в виду, что эти алиасы неотделимы друг от друга, так что если вызвать fclose() на таком потоке, то это также приведет к закрытию всех этих алиасов (см. примечание 3). 

Можно привязать дополнительные данные пользователя к потоку, используя fdev_set_udata(). Backend-функции put и get могут после этого достать данные пользователя, используя fdev_get_udata(), и действовать соответствующим образом. Например, по этому принципу одна функция put может использоваться, чтобы осуществлять обмен с двумя разными UART-ами, или функции put и get могут здесь хранить внутреннее состояние между вызовами. 

Строки формата в памяти программ (flash ROM). Все функции семейства printf и scanf поставляются в 2 вариантах, созданных для удобства: в виде стандартных имен, при этом подразумевается, что строки формата хранятся в SRAM, и также в виде версии с префиксом "_P", где ожидается, что строки формата размещены в flash ROM. Макрос PSTR (про который можно почитать в документации на < avr/pgmspace.h > [2]) очень удобен для декларирования этих строк формата. 

Запуск stdio без malloc(). По умолчанию  fdevopen() требует наличия malloc(). Как это часто нежелательно в условиях использования ограниченных по ресурсам микроконтроллеров, может быть предоставлена опция для запуска без использования malloc(). 

Предоставляется макрос fdev_setup_stream(), чтобы подготовить предоставленный пользователем буфер FILE для работы с stdio. Пример: 

#include < stdio.h >
 
static int uart_putchar(char c, FILE *stream);
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
 
static int uart_putchar(char c, FILE *stream)
{
   if (c == '\n')
      uart_putchar('\r', stream);
   loop_until_bit_is_set(UCSRA, UDRE);
   UDR = c;
   return 0;
}
 
int main(void)
{
   init_uart();
   stdout = &mystdout;
   printf("Hello, world!\n");
   return 0;
}

Этот пример использует форму инициализатора FDEV_SETUP_STREAM() вместо функции наподобие fdev_setup_stream(), так что все инициализации данных выполняется кодом start-up подсистемы языка C. 

Если потоки, инициализированные таким способом, больше не нужны, то они могут быть уничтожены первым вызовом макроса fdev_close(), что уничтожит сам объект. Для таких потоков не должен использоваться вызов fclose(). В то время как вызов fclose() сам по себе безопасен, он приведет к неопределенной ссылке на free(), что приведет к привязке линкером модуля malloc к приложению.

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

Это обычно следует принципу организации Unix: если устройство, такое как терминал, нуждается в специальной обработке, то это область функционала, которую должен предоставить драйвер устройства. Таким образом, простая функция, которая подойдет в качестве put() для fdevopen(), и которая работает через интерфейс UART, может выглядеть примерно так: 

int uart_putchar(char c, FILE *stream)
{
   if (c == '\n')
      uart_putchar('\r');
   loop_until_bit_is_set(UCSRA, UDRE);
   UDR = c;
   return 0;
}

 

Показанная в примечании 2 реализация была выбрана потому, что цена поддержки алиаса значительно меньше, чем затраты на поддержание полных копий каждого потока. Но в некоторых случаях предоставление полной реализации набора стандартных потоков может оказаться полезным. Написание printf() вместо fprintf(mystream, ...) не только экономит силы по вводу символов, но поскольку avr-gcc должен передать все аргументы функций переменного аргумента через стек (в противоположность к передаче их в регистрах для функций с фиксированным количеством параметров), возможность передать на один параметр меньше, подразумевая stdin или stdout, также сохранит некоторое количество рабочего времени процессора. 

[Документация на макросы

#define _FDEV_EOF   (-2)

Код возврата для конца файла (end-of-file) во время чтения устройства. Для использования в функции get после fdevopen(). 

#define _FDEV_ERR   (-1) 

Код возврата для события ошибки во время чтения устройства. Для использования в функции get после fdevopen(). 

#define _FDEV_SETUP_READ   __SRD 

fdev_setup_stream() на чтение 

#define _FDEV_SETUP_RW   (__SRD|__SWR) 

fdev_setup_stream() на чтение и запись 

#define _FDEV_SETUP_WRITE   __SWR

fdev_setup_stream() на запись 

#define EOF   (-1) 

EOF декларирует значение, которое будет возвращено различными стандартными функциями ввода/вывода IO в случае ошибки. Поскольку платформа AVR (в настоящий момент) не содержит абстракции для действительных файлов, использование по назначению "end of file" (конец файла) здесь несколько бессмысленно. 

#define fdev_close () 

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

#define fdev_get_udata ( stream )  ((stream)->udata) 

Этот макрос получает указатель на данные, определенные пользователем, для объекта потока FILE.

#define fdev_set_udata ( stream,
                         u 
                       ) do { (stream)->udata = u; } while(0)

Этот макрос вставляет указатель на данные, определяемые пользователем, в объект потока FILE. Данные пользователя могут быть полезны для отслеживания состояния в функциях put и get, предоставленных функцией fdevopen().

#define fdev_setup_stream ( stream,
                            put,
                            get,
                            rwflag 
                          )

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

Аргументы put и get идентичны тем, что нужно передать функции fdevopen(). 

Аргумент rwflag может получить оно из значений _FDEV_SETUP_READ, _FDEV_SETUP_WRITE или _FDEV_SETUP_RW соответственно для чтения, записи или чтения/записи содержимого потока. 

Примечание: функцией fdev_setup_stream() не будет выполнено назначение для стандартных потоков. Если используются стандартные потоки, то они нуждаются в назначении пользователем. См. также раздел "Запуск stdio без malloc()". 

#define FDEV_SETUP_STREAM  ( put,
                             get,
                             rwflag 
                           )

Инициализатор для потока stdio, предоставленного пользователем. Этот макрос работает подобно fdev_setup_stream(), однако он используется для инициализации переменной типа FILE. 

Остальные используемые аргументы разъяснены в описании fdev_setup_stream(). 

#define getc ( __stream ) fgetc(__stream)

Этот макрос getc используется как "быстрая" реализация макроса с функционалом, идентичным fgetc(). По соображениям экономии памяти в avr-libc он просто дает алиас на функцию fgetc. 

#define getchar ( void ) fgetc(stdin) 

Этот макрос getchar читает символ из stdin. Возвращаемые значения и обработка ошибок идентична fgetc(). 

#define putc ( __c,
               __stream 
             ) fputc(__c, __stream)

Этот макрос putc используется как "быстрая" реализация для функционала, идентичного fputc(). По соображениям экономии памяти в avr-libc он просто дает алиас на функцию fputc. 

#define putchar ( __c ) fputc(__c, stdout) 

Этот макрос putchar посылает символ c в stdout. 

#define stderr (__iob[2])

Поток, предназначенный для вывода ошибок. Если это не переназначено специально, идентичен stdout. Если stderr должен указывать на другой поток, результат другого fdevopen() должен быть явно назначен на этот макрос без закрытия предыдущего stderr (поскольку это также закроет stdout).

#define stdin (__iob[0])

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

#define stdout (__iob[1])

Поток, который будет использоваться как поток вывода для упрощенных функций, не получающих поток в аргументах. Первый поток, открытый на запись с использованием fdevopen() будет привязан к обоим потокам stdin и stderr. 

[Документация по функциям

void clearerr ( FILE * __stream )

Очистка флагов ошибки и end-of-file потока. 

int fclose ( FILE * __stream )

Эта функция закрывает поток, что запрещает его дальнейшее использование для ввода/вывода. Когда Вы используете fdevopen() для настройки потока, нужно вызвать fclose() для завершения потока, чтобы освободить внутренние ресурсы. 

Если поток был создан с использованием fdev_setup_stream() или FDEV_SETUP_STREAM(), вместо этого используйте fdev_close(). 

В настоящий момент fclose всегда возвращает 0 (успех). 

FILE* fdevopen ( int(*)(char, FILE *) put,
                 int(*)(FILE *)       get 
               )

Эта функция заменяет fopen(). Она открывает поток для устройства, где действительная реализация взаимодействия с устройством должна быть предоставлена приложением. Если результат успешный, то функция вернет указатель на структуру на открытый поток (stream). Причины для возможных возвратов ошибки в настоящий момент могут быть, если либо не предоставлены аргументы put или get, что означает открытие потока без возможности ввода/вывода как такового, или из-за недостатка динамической памяти, чтобы можно было создать новый поток. 

Если предоставлен указатель на функцию put, поток открывается на запись. Функция, которая передана как аргумент put, должна принимать 2 аргумента: первый символ для записи в устройство, второй указатель на FILE, и должна вернуть 0 если вывод был успешным, и не ноль, если символ не был отправлен в устройство. 

Если предоставлен указатель на функцию get, поток открывается на чтение. Функция, переданная как get, должна получать в единственном аргументе указатель на FILE, и возвращать символ из устройства, переданный как тип int. Если произошла ошибка при попытке чтения устройства, функция get должна вернуть _FDEV_ERR. Если произошло событие end-of-file во время чтения устройства, то get должна вернуть _FDEV_EOF. 

Если предоставлены обе функции put и get, поток откроется на чтение и запись своего содержимого. 

Первый открытый поток на чтение будет назначен на stdin, и первый открытый на запись будет назначен на оба потока stdout и stderr. 

fdevopen() использует вызов calloc() (и следовательно malloc()), чтобы выделить ресурсы для хранения нового потока. 

Примечание: если макрос __STDIO_FDEVOPEN_COMPAT_12 был декларирован до подключения < stdio.h >, будет выбран прототип функции fdevopen(), обратно совместимый с avr-libc версии 1.2 и более ранних. Это предназначено исключительно для упрощения миграции без необходимости внесения немедленных изменений в исходный код. Не используйте это в новом коде. 

int feof ( FILE * __stream )

Проверка флага end-of-file потока. Этот флаг можно очистить только вызовом clearerr().

int ferror ( FILE * __stream )

Проверка флага ошибки потока. Этот флаг можно очистить только вызовом clearerr().

int fflush ( FILE * stream )

Сброс буферов потока (flush stream). В действительности это пустая операция, предоставленная только для совместимости кода, так как стандартная реализация ввода/вывода в настоящий момент не выполняет какую-либо буферизацию.

int fgetc ( FILE * __stream )

Функция fgetc читает символ из потока. Она вернет символ, или EOF в случае наступления события end-of-file или события ошибки. Чтобы различать эти ситуации, должны использоваться функции feof() или ferror().

char* fgets ( char * __str,
              int __size,
              FILE * __stream
            )

Читает некий объем данных из потока (как минимум 1 байт), пока не встретится символ новой строки, и сохранит принятые символы в буфере, на который указывает str. За исключением ситуации возникновении ошибки, строка будет завершена символом нуля (NUL character). 

Если произошла ошибка, то функция вернет указатель NULL и установит флаг ошибки потока, который можно проверить, используя вызов ferror(). Иначе будет возвращен указатель на строку. 

int fprintf ( FILE * __stream,
              const char * __fmt,
              ... 
            )

Функция fprintf выполняет форматированный вывод в поток. Подробнее см. vfprintf().

int fprintf_P ( FILE * __stream,
                const char * __fmt,
                ... 
              )

То же самое, что и fprintf(), но здесь используется хранение строки формата __fmt в памяти программ (FLASH).

int fputc ( int __c,
            FILE * __stream
          )

Функция fputc отправляет символ c (хотя он предоставлен типом int) в поток. Функция fputc вернет символ, или EOF в случае возникновения ошибки.

int fputs ( const char * __str,
            FILE * __stream 
          )

Записывает в поток __stream строку, на которую указывает __str. При успехе вернет 0, или EOF в случае ошибки.

int fputs_P ( const char * __str,
              FILE * __stream 
            )

То же самое, что и fputs(), но здесь используется хранение строки __str памяти программ (FLASH).

size_t fread ( void * __ptr,
               size_t __size,
               size_t __nmemb,
               FILE * __stream
             )

Читает __nmemb объектов, каждый размером __size, из потока __stream в буфер, на который указывает __ptr. Вернет количество успешно прочитанных объектов, т. е. __nmemb за исключением случаев возникновения ошибки или конца данных на потоке (end-of-file). Для разделения этих случаев нужно использовать функции feof() и ferror().

int fscanf ( FILE * __stream,
             const char * __fmt,
             ... 
           )

Функция fscanf выполняет форматированный ввод, беря входные данные из потока. Подробнее см. vfscanf().

int fscanf_P ( FILE * __stream,
               const char * __fmt,
               ... 
             )

То же самое, что и fscanf(), но строка формата берется из памяти программ (FLASH).

size_t fwrite ( const void * __ptr,
                size_t __size,
                size_t __nmemb,
                FILE * __stream 
              )

Запишет __nmemb объектов в поток, каждый объект размером __size. На первый байт первого объекта указывает __ptr. Возвращает количество успешно записанных объектов, например __nmemb если не было ошибок при выводе.

char* gets ( char * __str )

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

int printf ( const char * __fmt,
             ... 
           )

Функция printf выполняет форматированный вывод в поток stdout. Подробнее см. vfprintf().

int printf_P ( const char * __fmt,
               ... 
             )

Вариант printf(), который строку формата __fmt берет из памяти программ.

int puts ( const char * __str )

Запись строки, на которую указывает str, и завершающий символ новой строки (newline) в поток stdout.

int puts_P ( const char * __str )

То же самое, что и puts(), но здесь строка находится в памяти программ.

int scanf ( const char * __fmt,
            ... 
          )

Функция scanf выполняет форматированный ввод из потока stdin. Подробнее см. vfscanf().

int scanf_P ( const char * __fmt,
              ... 
            )

Вариант scanf(), где строка формата __fmt находится в памяти программ.

int snprintf (char * __s,
              size_t __n,
              const char * __fmt,
              ... 
             )

Работает аналогично sprintf(), но вместо этого подразумевается, что s бесконечного размера, так что будет в __s преобразовано не более __n символов (включая завершающий символ NUL). Возвращает количество символов, которое было записано в __s, если в ней было достаточно места.

int snprintf_P ( char * __s,
                 size_t __n,
                 const char * __fmt,
                 ... 
               )

Вариант snprintf(), где строка формата __fmt находится в памяти программ.

int sprintf ( char * __s,
              const char * __fmt,
              ... 
            )

Вариант printf(), который отправляет отформатированные символы в строку __s.

int sprintf_P ( char * __s,
                const char * __fmt,
                ... 
              )

Вариант sprintf(), который для хранения строки форматирования __fmt использует память программ.

int sscanf ( const char * __buf,
             const char * __fmt,
             ... 
           )

Функция sscanf выполняет форматированный ввод, читая входные данные из буфера, на который указывает buf. Подробнее см. vfscanf().

int sscanf_P ( const char * __buf,
               const char * __fmt,
               ... 
             )

Вариант sscanf(), использующий строку формата __fmt из памяти программ.

int ungetc ( int __c,
             FILE * __stream 
           )

Функция ungetc() проталкивает символ __c (преобразованный в unsigned char) обратно в поток ввода __stream. Этот вставленный обратно символ будет возвращен последующим чтением потока. В настоящий момент только один символ может быть вставлен в поток обратно. Функция ungetc() вернет символ, который был преобразован и вставлен, или EOF, если операция завершилась неудачно. Если значение аргумента __c равно EOF, то операция завершится по ошибке и поток останется нетронутым.

int vfprintf ( FILE * __stream,
               const char * __fmt,
               va_list __ap 
             )

В функции vfprintf сосредоточен центральный функционал семейства функций printf. Она выводит значения в поток под управлением строки формата, переданной через аргумент __fmt. Действительные значения для печати передаются через список аргументов изменяемого размера. 

Функция vfprintf вернет количество символов, которое было записано в поток, или EOF, если произошла ошибка. В настоящий момент такое может произойти только тогда, когда поток не был открыт на запись. 

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

• 0 или большее количество следующих флагов:

   # значение, которое должно быть преобразовано к "альтернативной форме". Для преобразований c, d, i, s, и u эта опция не дает никакого эффекта. Для преобразований o точность числа увеличивается так, чтобы количество выводимых символов увеличивалось (за исключением печати значения 0, которое имеет явную точность 0). Для преобразований x и X, не нулевой результат дает строку '0x' (или '0X' для преобразований X).

   0 (zero) дополнение слева нулями. Для всех преобразований, преобразованное значение будет дополнено слева нулями, а не пробелами. Если указана точность для числовых преобразований (d, i, o, u, i, x и X), флаг 0 игнорируется.

   - флаг отрицательного поля ширины; преобразуемое значение получает левое выравнивание по границе поля. Преобразованное значение дополняется справа пробелами, а не слева пробелами или нулями. Этот флаг отменяет 0, если они предоставлены оба.

   ' ' (пробел) перед положительным числом должен быть пробел, когда преобразуется число со знаком(d или i).

   + знак должен быть всегда размещен перед числом, когда выполняется преобразование числа со знаком. Этот флаг отменяет флаг 'пробел', если они указаны вместе.

• Необязательная строка цифр, указывающая минимальную длину поля. Если преобразованное значение короче, чем указанная ширина поля, то она будет дополнена слева пробелами (или справа, если дан флаг левого выравнивания), чтобы заполнить поле до указанной ширины.

• Необязательная точность (precision), указанная через точку '.', за которой идет необязательная строка цифр. Если строка цифро опущена, то подразумевается точность 0. Это дает минимальное количество цифр для преобразований d, i, o, u, x и X, или максимальное количество символов для вывода преобразований s.

• Необязательный модификатор длины l или h, который указывает, что аргумент для преобразований d, i, o, u, x или X означает "long int" вместо int. Модификатор h игнорируется, поскольку "short int" эквивалентен int.

• Символ, который указывает тип преобразования (conversion specifier). 

Conversion specifier может быть следующий: 

d, i, o, u, x или X. Целый аргумент (int или подходящий вариант) преобразуется как знаковое десятичное (signed decimal, для d и i), беззнаковое восьмеричное (unsigned octal, для o), беззнаковое десятичное (unsigned decimal, для u), или беззнаковое шестнадцатеричное (unsigned hexadecimal, для x и X). Символы "abcdef" (маленькие) используются для преобразований x; буквы "ABCDEF" (большие) используются для преобразований X. Точность (precision) если она указана, дает минимальное количество появляющихся цифр; если у преобразованного значения получается меньше цифр, то слева добавляются нули.

p. Аргумент void * берется как беззнаковое целое (unsigned), и преобразуется так же, как по формату %#x.

c. Аргумент int преобразуется в argument unsigned char, и будет записан символ с результирующим кодом.

s. Аргумент char * интерпретируется как указатель на массив символов (указатель на строку). Символы из массива записываются до завершающего NUL (но сам NUL отбрасывается); если указана точность (precision), то будет записано не больше символов, чем указано. Если дана точность, то наличия в строке завершающего нуля не обязательно; если точность не указана, или если она больше, чем размер массива, то массив должен содержать в себе символ завершения строки (0, символ NUL).

%. Будет записан %. Нет аргументов для преобразования. Используется, если нужно вывести символ процента. Полная спецификация формата "%%".

e, E. Аргумент double будет округлен и преобразован в формат "[-]d.dddeA±dd", где одна цифра до десятичной точки, и количество цифр после десятичной точки равно точности (precision); если точность не указана, то берется точность 6; если указана точность 0, то не будет использоваться десятичная точка. Преобразование E использует букву 'E' (большая, а не маленькая 'e') для указания экспоненты exponent. Экспонента всегда содержит 2 цифры; если значение равно 0, то экспонента 00.

f, F. Аргумент double округляется и преобразуется в формат "[-]ddd.ddd", где количество цифр после десятичной токи равно указанной точности (precision). Если точность не указана, то берется точность 6; если точность явно указана 0, то не будет десятичной точки и цифр после неё. Если появляется десятичная точка, то перед ней будет указана хотя бы одна цифра.

g, G. Аргумент double преобразуется в стиле f или e (F или E для преобразований G). Точность (precision) указывает количество значащих цифр. Если точность опущена, то будет выдано 6 цифр; если precision 0, то это обрабатывается как 1. Стиль e используется, если экспонента от преобразования меньше, чем -4 или больше или равна точности precision. Завершающие нули удаляются из дробной части результата; десятичная точка появляется только если после неё появляется хотя бы одна цифра.

S. То же самое, что и формат s, за исключением того, что ожидается указатель на место в памяти программ (ROM, FLASH), а не в RAM. 

Ни в коем случае не будет сделано усечение по причине указания слишком малого поля ширины; если результат преобразования больше, чем поле ширины, то поле будет расширено до того, чтобы вместить результат преобразования.

[Управление библиотеками stdio через опции линкера]

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

-Wl,-u,vfprintf -lprintf_min

Если нужна полная функциональность, включая обработку преобразований floating point, то нужно использовать следующие опции:

-Wl,-u,vfprintf -lprintf_flt -lm

Есть ограничения: указанная ширина и точность должна быть самое большее 255.

Примечания:

• Для преобразований floating-point, если Вы линковали минимальную версию vfprintf(), будет выведен символ ? и аргумент double будет пропущен, так что сбоя вывода не произойдет. Для версии по умолчанию поле ширины и опция "дополнение слева" ( символ - ) работать не будут.

• Модификатор длины hh игнорируется (аргумент char интерпретируется как int). Точнее, эта реализация не проверяет количество символов h в формате.

• Однако модификатор длины ll оборвет вывод, так как эта реализация не обрабатывает аргументы long long.

• Поле переменной ширины или точности (variable width, precision field с символом *) в этой версии не реализовано, и такой формат оборвет вывод. 

int vfprintf_P ( FILE * __stream,
                 const char * __fmt,
                 va_list __ap 
               )

Вариант vfprintf(), который использует строку формата из памяти программ.

int vfscanf ( FILE * stream,
              const char * fmt,
              va_list ap 
            )

[Форматированный ввод]

Эта функция является основой семейства функций scanf. Символы читаются из потока и обрабатываются так, как это описано в fmt. Результат преобразований будет присвоен параметрам, переданным через ap. 

Строка формата fmt сканируется в поиске спецификаций преобразования. Все, что не соответствует спецификации формата, берется как текст, буквально поступающий со входа. Пробелы в строке формата будут соответствовать пробелам в данных (пробелов может и не быть), все другие символы соответствуют только сами себе. Обработка будет оборвана, как только данные или строка формата больше не соответствуют, или произошла ошибка или событие конца данных на потоке (события error или end-of-file). 

Большинство преобразований пропускают лидирующие пробелы перед началом действительного преобразования. Спецификаторы преобразования вводятся символом %. За знаком % могут идти следующие опции: 

* означает, что преобразование должно быть выполнено, но результат должен быть отброшен; из ap не будут обработаны параметры.

h означает, что аргумент указывает на short int (а не на int).

hh означает, что аргумент указывает на char (а не на int).

l показывает, что аргумент указывает на long int (а не на int для преобразований целого типа) или на double (для преобразований floating point). 

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

Поддерживаются следующие флаги преобразования: 

%. Соответствует просто символу %. Это не преобразование.

d. Соответствует опционально знаковому десятичному целому числу; следующий указатель в аргументах должен указывать на int.

i. Соответствует опционально знаковому десятичному целому числу; следующий указатель в аргументах должен указывать на int. Целое число читается по базе 16, если оно начинается на 0x или 0X, по базе 8 если начитается на 0, иначе по базе 10. Используются только те символы, которые соответствуют базе представления чисел.

o. Соответствует целому восьмеричному; указатель в аргументах должен быть указателем на unsigned int.

u. Соответствует на опционально знаковое десятичное целое; указатель в аргументах должен указывать на unsigned int.

x. Соответствует опционально знаковому шестнадцатеричному целому; указатель в аргументах должен указывать на unsigned int.

f. Соответствует опционально знаковому числу с плавающей точкой; указатель в аргументах должен указывать на float.

e, g, F, E, G. Эквивалент f.

s. Соответствует последовательности символов, кроме пробела; указатель в аргументах должен указывать на массив char, и массив должен быть достаточно велик, чтобы принять всю последовательность символов плюс завершающий нулевой символ NUL. Ввод строки завершается на пробеле, или определяется по максимальному полю ширины - что случится раньше.

c. Соответствует последовательности с неким количеством символов (по умолчанию 1 символ); указатель в аргументах должен указывать на char, и должно быть достаточно места для всех символов (завершающий NUL добавлен не будет). Подавляется обычный пропуск лидирующих пробелов. Чтобы пропустить лидирующие пробелы, используйте в формате явно указанные пробелы.

[. Соответствует не пустой последовательности символов от указанного набора допустимых символов; указатель в аргументах должен указывать на char, и должно быть достаточно места для всех символов в строке, плюс завершающий символ NUL. Подавляется обычный пропуск лидирующих пробелов. Последовательность будет составлена из символов этого набора (кроме тех, которых нет в наборе); набор определяется символами между открывающей скобкой [ и закрывающей скобкой ]. Набор исключает те символы, если первый символ после открывающей скобки был ^. Чтобы добавить в набор закрывающую скобку, сделайте её первой после открывающей скобки или ^; любая другая позиция для закрывающей скобки будет означать конец спецификации набора. Символ дефиса '-' также носит специальный смысл; когда он размещен между двумя символами, то это добавит в набор все символы, которые находятся по коду между ними. Чтобы добавить в набор сам дефис, сделайте его последним в наборе, указав перед закрывающей скобкой. Например, [^]0-9-] любой набор символов кроме закрывающей скобки, всех цифр от 0 до 9 и дефиса. Строка, которая завершится появлением любого символа не в наборе (или, если применен ^, то в наборе), или когда поле ширины закончится. Имейте в виду, что использование этого преобразование увеличивает использование памяти под стек.

p. Соответствует значению указателя (как напечатано для p в printf()); указатель в аргументах должен указывать на void.

n. Ничего не ожидается; вместо этого будет выбрано из ввода некоторое количество символов, и помещено по следующему указателю в аргументах, который должен быть указателем на int. Это не будет преобразованием, хотя оно может быть подавлено флагом *. 

Эти функции возвратят количество назначенных входных элементов в аргументах, количество которых может быть меньше или даже 0, когда даст сбой совпадение. 0 говорит о том, что ни одна входная переменная не была назначена; обычно это происходит из-за несоответствующего входного символа, к примеру был алфавитный символ (буква) для преобразования d. Будет возвращено значение EOF, если на входе была ошибка до того, как произошло любое из преобразований, как например произошло событие окончания данных на потоке (end-of-file). Если произошло событие end-of-file уже после того, как преобразование началось, то будет возвращено действительное количество успешных преобразований. 

По умолчанию допустимы все вышеописанные преобразования, за исключением преобразований floating-point и ширины, ограниченной 255 символами. Преобразование float-point будет доступно в расширенной версии, предоставленной библиотекой libscanf_flt.a. Также в этом случае ширина не ограничена (на самом деле есть ограничение в 65535 символов). Чтобы прилинковать к программе расширенную версию, используйте для стадии линковки следующие флаги компилятора:

-Wl,-u,vfscanf -lscanf_flt -lm

Третья версия доступна для случаев работы в ограниченных по памяти условиях. В дополнение к ограничениям стандартной версии, эта версия не реализует спецификацию %[. Она предоставлена в библиотеке libscanf_min.a, и может быть запрошена следующими опциями на стадии линковки:

-Wl,-u,vfscanf -lscanf_min -lm

 

int vfscanf_P ( FILE * __stream,
                const char * __fmt,
                va_list __ap 
              )

Вариант vfscanf(), использующий строку формата из памяти программ.

int vprintf ( const char * __fmt,
              va_list __ap 
            )

Функция vprintf выполняет форматированный вывод в поток stdout, получая тот же список переменных аргумента, как и в функции vfprintf(). Подробнее см. описание vfprintf().

int vscanf ( const char * __fmt,
             va_list __ap 
           )

Функция vscanf выполняет форматированный ввод из потока stdin, получая тот же список переменных в аргументах, как и vfscanf(). Подробнее см. описание vfscanf().

int vsnprintf ( char * __s,
                size_t __n,
                const char * __fmt,
                va_list ap 
              )

То же самое, что и vsprintf(), но вместо строки бесконечного размера здесь подразумевается, что s имеет конечный объем, так что в строку s не может быть преобразовано больше n символов (включая символ NUL). Функция возвратит количество записанных символов, если было достаточно свободного места.

int vsnprintf_P ( char * __s,
                  size_t __n,
                  const char * __fmt,
                  va_list ap 
                )

Вариант vsnprintf(), который использует строку формата из памяти программ.

int vsprintf ( char * __s,
               const char * __fmt,
               va_list ap 
             )

То же самое, что и sprintf() но получает в аргументах список переменных, который может быть произвольной длины.

int vsprintf_P ( char * __s,
                 const char * __fmt,
                 va_list ap 
               )

Вариант vsprintf(), который использует строку формата из памяти программ.

[Ссылки]

1. Standard IO facilities site:nongnu.org.
2. AVR Studio +gcc: как разместить строки и константы на flash.
3. Секреты printf.
4. IAR EWB ARM: форматированный вывод printf библиотеки DLIB.

 

Добавить комментарий


Защитный код
Обновить

Top of Page