fprintf — Записывает отформатированную строку в поток


Содержание

Разница между fprintf, printf и sprintf?

Может кто-нибудь объяснить простым языком о различиях между printf , fprintf и sprintf с примерами?

В каком потоке это?

Я действительно запутался между тремя из них, когда читал о «Обработка файлов в C».

8 ответов

В C «поток» является абстракцией; с точки зрения программы это просто производитель (входной поток) или потребитель (выходной поток) байтов. Он может соответствовать файлу на диске, каналу, вашему терминалу или другому устройству, например, принтеру или tty. Тип FILE содержит информацию о потоке. Обычно вы не связываетесь непосредственно с содержимым объекта FILE , вы просто передаете указатель на него различным подпрограммам ввода-вывода.

Существует три стандартных потока: stdin — указатель на стандартный поток ввода, stdout — указатель на стандартный поток вывода, а stderr — указатель на поток вывода стандартной ошибки. В интерактивном сеансе эти три обычно относятся к вашей консоли, хотя вы можете перенаправить их, чтобы указать на другие файлы или устройства:

В этом примере stdin теперь указывает на inputfile.dat , stdout указывает на output.txt , а stderr указывает на errors.txt .

fprintf записывает форматированный текст в указанный вами выходной поток.

printf эквивалентен записи fprintf(stdout, . ) и записывает форматированный текст туда, куда в данный момент указывает стандартный поток вывода.

sprintf записывает форматированный текст в массив char , а не в поток.

получить отформатированную строку в переменную

Есть ли функция C (или способ), чтобы получить отформатированную строку как нормальную строку?

я хочу printf() выходной «this is a message» в char* переменная. Это должно быть просто, но я не приду с чем-то, кроме как записать это в файл и прочитать или объединить столько раз, сколько необходимо.

Итак, есть ли функция или простой способ поместить любую отформатированную строку в одну строковую переменную в C?

3 ответа

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

Вы хотите либо sprintf или snprintf :

использование

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

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

Почему snprintf безопасный

snprintf записывает вывод в строку str , под управлением формата строки формата, который определяет, как последующие аргументы преобразуются для вывода. Это похоже на sprintf(3) , Кроме этого size указывает максимальное количество символов для производства. Конечный нулевой символ засчитывается в этот предел, поэтому вы должны выделить как минимум size символы для str .

Если size ноль, ничего не написано и str может быть нулем. В противном случае, вывод символов за пределы n-1 st сбрасываются, а не пишутся str , и нулевой символ пишется в конце символов, фактически записанных в str . Если копирование происходит между объектами, которые перекрываются, поведение не определено.

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

Это, snprintf защищает программистов от переполнения буфера, в то время как sprintf не делает.

Обратите внимание, что var должен быть достаточно большим, чтобы хранить окончательную строку (включая терминатор строки).

Да, функция, которую вы ищете sprintf() / snprintf() .

квотирование C11 , глава § 7.21.6.5, snprintf() функция

int snprintf(char * restrict s, size_t n, const char * restrict format, . );

snprintf функция эквивалентна fprintf , за исключением того, что вывод записывается в массив (определяется аргументом s ), а не в поток.

Он записывает отформатированную строку в массив, предоставленный в качестве первого аргумента.

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

Разница между fprintf, printf и sprintf?

Может ли кто-нибудь объяснить простым языком о различиях между printf , fprintf и sprintf с примерами?

В каком потоке это?

Я действительно запутался между тремя из них, читая о «Обработка файлов в C».

В C «поток» является абстракцией; с точки зрения программы это просто производитель (входной поток) или потребитель (выходной поток) байтов. Он может соответствовать файлу на диске, каналу, вашему терминалу или другому устройству, например принтеру или tty. Тип FILE содержит информацию о потоке. Обычно вы не связываетесь непосредственно с содержимым объекта FILE , вы просто передаете указатель на него различным подпрограммам ввода/вывода.

Существует три стандартных потока: stdin — указатель на стандартный поток ввода, stdout — указатель на стандартный поток вывода, а stderr — указатель на стандартный поток вывода ошибок. В интерактивном сеансе эти три обычно относятся к вашей консоли, хотя вы можете перенаправить их, чтобы указать на другие файлы или устройства:

В этом примере stdin теперь указывает на inputfile.dat , stdout указывает на output.txt , а stderr указывает на errors.txt .

fprintf записывает форматированный текст в указанный вами выходной поток.

printf эквивалентен записи fprintf(stdout, . ) и записывает форматированный текст туда, куда в данный момент указывает стандартный поток вывода.

sprintf записывает форматированный текст в массив char , а не в поток.

printf выводит в стандартный поток вывода ( stdout )

fprintf идет в дескриптор файла ( FILE* )

sprintf отправляется в выделенный вами буфер. ( char* )

Описание функций языка Си

All | _ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z

printf – вывод форматированной строки в стандартный поток вывода.

#include
int printf (const char *format, . );

format – указатель на строку c описанием формата.

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

Функция printf выводит в стандартный поток вывода строку отформатированную в соответствии с правилами, указанными в строке, на которую указывает аргумент format.

Правила задаются набором трех типов директив:
1. Обычные символы (кроме ‘%’ и ‘\’), которые выводятся без изменения;
2. Спецификаторы формата;
3. Специальные сиволы.

Каждый спецификатор формата начинается с символа ‘%’ и имеет следующий формат:

Спецификатор формата может иметь 0 или более [флагов], которые могут принемать значенияуказанные в таблице 1.

Таблица 1.

Флаг Назначение флага
— (дефис) Результат преобразования выравнивается по левому краю (по умолчанию — по правому краю)
+ Перед положительными числами выводится знак ‘+’, а перед отрицательыыми — знак ‘-‘ (по умолчанию выводится только знак ‘-‘ перед отрицательыми числами)
‘ ‘ (пробел) Если не указан модификатор ‘+’, то перед положительными числами, на месте знака числа, будет выводиться пробел.
# Использовать альтернативную форму представления выводимого числа. При выводе чисел в шестнадцатеричном формате (преобразователь ‘х’ или ‘Х’) перед числом будет указываться 0х или 0Х соответственно. При выводе чисел в восьмеричном формате (преобразователь ‘о’)перед числом будет указываться 0. При выводе чисел с плавующей точкой (преобразователи e, E, f, g и G) всегда будет содержаться десятичная точка (по умолчанию десятичная точка выводится только при ненулевой дробной части). При использовании преобразователей g и G хвостовые нули не будут удаляться (по умолчанию удаляются).
Если не указан флаг ‘-‘, то слева от выводимого числа будут выведены символы ‘0’ для подгона числа к указанной ширене. Если для преобразователей d, i, o, x или X указана точность, то флаг 0 игнорируется.
Илон Маск рекомендует:  Что такое код kylix

Спецификатор [флаги] можно не указывать.

Спецификатор [ширина] задаёт минимальный размер выводимого числа в символах. Если количество символов в выводимом числе меньше указанной минимальной ширины, то недостоющее количество символов заполняется нулями или пробелами слева или справа в зависимости от указанных флагов. Ширина указывается либо целым числом, либо символом * с последующим указанием имени переменной типа int, содержащей значение ширины, перед аргументом к которому он относится. Если аргумент имеет отрицательное значение, то он эквивалентен соответствующему положительному значению с флагом «-«.

Спецификатор [ширина] можно не указывать.

Действия спецификатора [точность] зависит от типа выводимого числа:

— Для типов d, i, o, u, x, X определяет минимальное число выводимых цифр. Если количество выводимых цифр в числе меньше, чем указано в спецификаторе [точность], то выводимое число будет дополнено нулями слева. Например, если при выводе числа 126 указать точность 4, то на экран будет выведено число 0126

— Для типов a, A, e, E, f, F определяет количество выводимых цифр после запятой. Если в выводимом числе количество значимых цифр после запятой меньше указанной точности, то недостающие символы выводятся нулями справа от числа. Если больше, то лишние цифры не выводятся. Например, если при выводе числа 126.345 указать точность 2, будет выведено на экран число 126.34, а если указать точность 5, то на экран будет выведено число 126.34500.


— Для типов g и G определяет максимальное выводимое число цифр. Например, если при выводе числа 126.345 указать точность 4, будет выведено на экран число 126.3. Если при выводе числа 1242679.23 указать точность 3, будет выведено на экран число 1.24е+06.

Точность указывается в виде символа точка, за которым следует десятичное число или символ «*», с последующим указанием имени переменной типа int, содержащей значение точности, перед аргументом к которому он относится.

Спецификатор [точность] можно не указывать.

Спецификатор [модификаторы] определяет размер выводимых данных (char, short, long, longlong). Спецификаторы используются для вывода чисел типа: char, short int, long int, long long int, long double или для явного преобразования выводимых данных. Например, если имеется переменная типа int, а необходимо вывести ее как short int. Доступные модификаторы приведены в таблице 2.

Таблица 2.

Модификатор Назначение модификатора
h Для вывода числа типа short int или unsigned short int. Или для явного преобразования при выводе целочисленного числа к типу short int или unsigned short int. Используется совместно с типами преобразования:d, i, o, u, x и X, n.
hh Для вывода числа типа char или unsigned char. Или для явного преобразования при выводе целочисленного числа к типу char или unsigned char. Используется совместно с типами преобразования:d, i, o, u, x и X, n.
l Для вывода числа типа long int или unsigned long int. Или для явного преобразования при выводе целочисленного числа к типу long int или unsigned long int. Используется совместно с типами преобразования:d, i, o, u, x и X, n.
ll Для вывода числа типа long long int или unsigned long long int. Или для явного преобразования при выводе целочисленного числа к типу long long int или unsigned long long int. Используется совместно с типами преобразования:d, i, o, u, x и X, n.
L Для вывода числа типа long double. Или для явного преобразования при выводе числа c плавающей точкой к типу long double. Используется совместно с типами преобразования:e, E, f, g и G.

Спецификатор [тип преобразования] определяет как надо итерпритировать и выводить число, например как знаковое целочисленное в десятичном виде, или беззнаковое целочисленное в шестнадцатеричном, или как число с плавающей точкой и так далее. Перечень доступных преобразований и их описание указано в таблице 3.

Таблица 3.

Тип преобразования Назначение преобразования
d,i Вывод целого числа со знаком в десятичной систем счисления. По умолчанию выводится число размером sizeof( int ), с правым выравниванием, указанием знака только для отрицательных чисел.
u Вывод целого числа без знака в десятичной систем счисления. По умолчанию выводится число размером sizeof( int ), с правым выравниванием.
o Вывод целого числа без знака в восьмеричной систем счисления. По умолчанию выводится число размером sizeof( int ), с правым выравниванием.
x, X Вывод целого числа без знака в шестнадцетеричной систем счисления. Причем для преобразования x используются символы abcdef, а для X — символы ABCDEF. По умолчанию выводится число размером sizeof( int ), с правым выравниванием.
f, F Вывод числа с плавающей точкой в виде [-]dddd.ddd. По умолчанию выводится число с точностью 6, если число по модулю меньше единицы, то пред десятично точкой выводится ноль, знак указывается только для отрицательных чисел, с правым выравниванием. Размер по умолчанию sizeof( double ).
e, E Вывод числа с плавающей точкой в экспоненциальной форме записи, в виде [-]dddd.ddde±dd, причем для модификатора e используется символ e, а для модификатора E — символ E. По умолчанию выводится число с точностью 6, если число по модулю меньше еденицы, то пред десятично точкой выводится ноль, знак указывается только для отрицательных чисел, с правым выравниванием. После символа «e» (или «E») всегда выводится две цифры (они равны 0, если аргумент равен 0).
g, G Вывод числа с плавающей точкой в форме зависищей от величины цисла. Например число 345.26 будет выведено как 345.26, а число 1344527.434 как 1.34453e+06. По умолчанию выводится 6 значащих цифр числа с округлением, если число по модулю меньше еденицы, то пред десятично точкой выводится ноль, знак указывается только для отрицательных чисел, с правым выравниванием.
a, A Вывод числа с плавающей точкой в шестнадцатеричном фомрате, в экспоненциальной форме записи. Например число 137.434 будет выведено как 0x1.12de353f7ced9p+7. Экспонента обозначается символом p. Для модификатора a используется символ p, а для модификатора A — символ P.По умолчанию знак указывается только для отрицательных чисел, выравнивание — правое.
с Вывод символа, соответстветсвующего числу указанному в аргументе функции. По умолчанию число приводится к типу unsigned char.
s Вывод строки, на которую ссылается указатель в аргументе функции printf. Строка выводится пока не будет встречен символ конец строки (/0). По умолчанию строка должна обозначаться как char*. Если указан модификатор l, то строка интерпитируется как wchar_t*. Для функции wprintf строка по умолчанию обрабатывается как wchar_t*.
S Аналогичен преобразованию s с модификатором l (ls).
p Вывод указателя. Результат ввода зависит от архитектуры и используемого компилятрора. Например, на 16 битной платформе MS-DOS вывод будет иметь вид типа FFAB:1402, а на 32-битной платформе с плоской адресацией — 00FC0120.
n Запись по адресу, указанному в аргументе функции, количества выведенных символов функцией printf до встречи преобразователя %n. При обработке преобразователя %n никакого вывода символов не производится.

Для форматирования вывода в функции printf предусмотрен набор специальных симовлов. Перечень специальных символов приведен в таблице 4.

Преобразование регистратора в стиле printf с буфером стека в поток строк

В настоящее время у меня есть решение для ведения журнала, которое определяет макрос как это:

Параметр сообщения const char * использует формат, аналогичный printf, например:Меня зовут% s, и я% u.«

Фактический метод ведения журнала, который я использую, заключается в том, что я объявляю буфер символов [2048] переменная в стеке, и я использую vsnprintf для преобразования моего параметра va_list, как определено в моем параметре сообщения, в мой буфер. Однако, так как я часто обрабатываю значения, которые могут быть значительно больше, чем 2k, мой буфер всегда усекается, поэтому я подумал о том, чтобы переработать все вокруг stringstream, который был бы более гибким.

Fprintf — Записывает отформатированную строку в поток

1720 просмотра

3 ответа

34 Репутация автора

Есть ли функция C (или способ), чтобы получить отформатированную строку как нормальную строку?

Я хочу printf() s вывод «this is a message» в char* переменную. Это должно быть просто, но я не приду с чем-то, кроме как записать это в файл и прочитать или объединить столько раз, сколько необходимо.

Илон Маск рекомендует:  Программное вращение изображений

Итак, есть ли функция или простой способ поместить любую отформатированную строку в одну строковую переменную в C?

Ответы (3)

3 плюса

115636 Репутация автора

Да, функция, на которую вы рассчитываете — это sprintf() / snprintf() .

Цитирование C11 , глава §7.21.6.5, функция snprintf()

int snprintf(char * restrict s, size_t n, const char * restrict format, . );

snprintf Функция эквивалентна fprintf , за исключением того, что выходной сигнал записывается в массив (указанный аргумент s ) , а не к потоку.

Он записывает отформатированную строку в массив, предоставленный в качестве первого аргумента.

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

4 плюса

88551 Репутация автора

Обратите внимание, что он var должен быть достаточно большим, чтобы хранить окончательную строку (включая терминатор строки).

5 плюса

25597 Репутация автора

Вы хотите sprintf либо snprintf :

использование

Обратите внимание, что snprintf это безопаснее, поскольку он обрезает строку до соответствующей длины встречающегося переполнения.

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

Почему snprintf безопасно

snprintf записывает вывод в строку str под управлением формата строки формата, который указывает, как последующие аргументы преобразуются для вывода. Это похоже на sprintf(3) , за исключением того, что size указывает максимальное количество символов для производства. Конечный нулевой символ засчитывается в этот предел, поэтому вы должны выделить как минимум size символы str .

Если size ноль, ничего не написано, и str может быть нулем. В противном случае выходные символы за пределами n-1 st отбрасываются, а не записываются str , и нулевой символ записывается в конце фактически записанных символов str . Если копирование происходит между объектами, которые перекрываются, поведение не определено.

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

То есть snprintf защищает программистов от переполнения буфера, а sprintf не защищает .

Самые частые грабли при использовании printf в программах под микроконтроллеры

Время от времени в моих проектах приходится применять printf в связке с последовательным портом (UART или абстракция над USB, имитирующая последовательный порт). И, как обычно, времени между его применениями проходит много и я успеваю напрочь забыть все нюансы, которые требуется учитывать, чтобы он нормально работал в крупном проекте.

В данной статье я собрал свой собственный топ нюансов, которые возникают при использовании printf в программах под микроконтроллеры, сортированный по очевидности от самых очевидных к полностью неочевидным.

Краткое введение

По сути для того, чтобы использовать printf в программах под микроконтроллеры, достаточно:

  • подключить заголовочный файл в коде проекта;
  • переопределить системную функцию _write на вывод в последовательный порт;
  • описать заглушки системных вызовов, которые требует компоновщик (_fork, _wait и прочие);
  • использовать printf вызов в проекте.

На деле же, не все так просто.

Описать нужно все заглушки, а не только используемые

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

Важно отметить, что все заглушки должны быть C функциями. Не C++ (или обернутые в extern «C»). Иначе компоновка пройдет неудачно (помним про изменение имен при сборке у G++).

В _write приходит по 1 символу

Несмотря на то, что в прототипе метода _write есть аргумент, передающий длину выводимого сообщения, он имеет значение 1 (на самом деле мы сами сделаем так, что он всегда будет 1, но об этом далее).

В интернете часто можно видеть вот такую реализацию этого метода:

У такой реализации есть следующие недостатки:

  • низкая производительность;
  • потоковая незащищенность;
  • невозможность использовать последовательный порт для других целей;

Низкая производительность

Здесь за непосредственную отправку с использованием dma отвечает объект класса uart — uart_1. Объект использует методы FreeRTOS для блокировки стороннего доступа к объекту на момент отправки данных из буфера (взятие и возвращение mutex-а). Таким образом, никто не может воспользоваться объектом uart-а во время отправки из другого потока.
Немного ссылок:

  • код функции _write в составе реального проекта здесь
  • интерфейс класса uart здесь
  • реализация интерфейса класса uart под stm32f4 здесь и здесь
  • создание экземпляра класса uart в составе проекта здесь

Потоковая незащищенность

Данная реализация так же остается потока незащищенной, поскольку никто не мешает в соседнем потоке FreeRTOS начать отправку в printf другой строки и тем самым перетереть отправляемый в данный момент буфер (mutex внутри uart-а защищает объект от использования в разных потоках, но не передаваемые им данные). В случае, если есть риск, что будет вызван printf другого потока, то требуется реализовать объект-прослойку, который будет блокировать доступ к printf целиком. В моем конкретном случае с printf взаимодействует лишь один поток, поэтому дополнительные усложнения лишь уменьшат производительность (постоянное взятие и отпуск mutex-а внутри прослойки).

Невозможность использовать последовательный порт для других целей


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

По умолчанию не работает вывод float

Если вы используете newlib-nano, то по умолчанию printf (а так же все их производные по типу sprintf/snprintf… и прочие) не поддерживают вывод float значений. Это легко решается добавлением в проект следующих флагов компоновщика.

Посмотреть полный перечень флагов можно здесь.

Программа зависает где-то в недрах printf

Это еще одна недоработка флагов компоновщика. Чтобы прошивка была скомпонована с нужной версией библиотеки нужно явно указать параметры процессора.

Посмотреть полный перечень флагов можно также здесь.

printf заставляет микроконтроллер попасть в hard fault

Проблемы со стеком

Эта проблема действительно проявляет себя при использовании FreeRTOS или любой другой ОС. Проблема заключается в использовании буфера. В первом пункте было сказано, что в _write приходит по 1 байту. Для того, чтобы это произошло, нужно в коде, перед первым использованием printf запретить использование буферизации.

Из описания функции следует, что можно установить так же и одно из следующих значений:

Однако это может привести к переполнению стека задачи (или прерывания, если вы вдруг очень нехороший человек, который вызывает printf из прерываний).

Чисто технически, можно очень аккуратно расставить для каждого потока стеки, однако такой подход требует тщательного планирования и ошибки, которые он несет в себе, трудно отловить. Куда более простым решением является прием по одному байту, складирование в собственный буфер с последующим выводом в требуемом формате, разобранный ранее.

Проблемы с _sbrk

Эта проблема была лично для меня самой неявной. И так, что мы знаем о _sbrk?

  • очередная заглушка, которую требуется реализовать для поддеркжи немалой части стандартных библиотек;
  • требуется для выделения памяти в куче;
  • используется всякими библиотечными методами по типу malloc, free.

Лично я в своих проектах в 95% случаев использую FreeRTOS с переопределенными методами new/delete/malloc, использующими кучу FreeRTOS. Так что когда я выделяю память, то уверен, что выделение идет в куче FreeRTOS, которая занимает заранее известное количество памяти в bss области. Посмотреть на прослойку можно здесь. Так что, чисто технически, никакой проблемы быть не должно. Функция просто не должна вызываться. Но давайте подумаем, если она вызовится, то где она попытается взять память?

Вспомним разметку оперативной памяти «классического» проекта под микроконтроллеры:

  • .data;
  • .bss;
  • пустое пространство;
  • начальный стек.

В data у нас начальные данные глобальных объектов (переменные, структуры и прочие глоабльные поля проекта). В bss — глобальные поля, имеющие начальное нулевое значение и, внимательно, куча FreeRTOS. Она представляет из себя просто массив в памяти. с которым потом работают методы из файла heap_x.c. Далее идет пустое пространство, после которого (вернее сказать с конца) располагается стек. Т.к. в моем проекте используется FreeRTOS, то данный стек используется только до момента запуска планировщика. И, таким образом, его использование, в большинстве случаев, ограничивается коллобайтом (на деле обычно 100 байт предел).

Илон Маск рекомендует:  Предопределённые константы ldap

Но где же тогда выделяется память с помощью _sbrk? Взглянем на то, какие переменные она использует из linker script-а.

Теперь найдем их в linker script-е (мой скрипт немного отличается от того, который предоставляет st, однако эта часть там примерно такая же):

То есть он использует память между стеком (1 кб от 0x20020000 вниз при 128 кб RAM) и bss.

Разобрались. Но ведь у нес есть переопределние методов malloc, free и прочих. Использовать _sbrk ведь ведь не обязательно? Как оказалось, обязательно. Причем этот метод использует не printf, а метод для установки режима буферизации — setvbuf (вернее сказать _malloc_r, который в библиотеке объявлен не как слабая функция. В отличии от malloc, который можно легко заменить).

Так как я был уверен, что sbrk не используется, расположил кучу FreeRTOS (секцию bss) вплотную к стеку (поскольку точно знал, что стека используется раз в 10 меньше, чем требуется).

Решения проблемы 3:

  • сделать некоторый отступ между bss и стеком;
  • переопределить _malloc_r, чтобы _sbrk не вызывался (отделить от библиотеки один метод);
  • переписать sbrk через malloc и free.

Я остановился на первом варианте, поскольку безопасно заменить стандартный _malloc_r (который находится внутри libg_nano.a(lib_a-nano-mallocr.o)) мне не удалось (метод не объявлен как __attribute__ ((weak)), а исключить только единственную функцию из биюлиотеки при компановке мне не удалось). Переписывать же sbrk ради одного вызова — очень не хотелось.

Конечным решением стало выделение отдельных разделов в RAM под начальный стек и _sbrk. Это гарантирует, что на этапе компановки секции не налезут друг на друга. Внутри sbrk так же есть проверка на выход за пределы секции. Пришлось внести небольшую правку, чтобы при детектировании перехода за границу поток зависал в while цикле (посколько использование sbrk происходит только на начальном этапе инициализации и должно быть обработано на этапе отладки устройства).

Посмотреть на mem.ld и section.ld можно в моем проекте-песочнице в этом коммите.

UPD 12.07.2020: поправил перечень флагов для работы printf с float значениями. Поправил ссылку на рабочий CMakeLists с поправленными флагами компиляции и компоновки (были нюансы с тем, что флаги нужно перечислять по одному и через «;», при этом на одной все строке или на разных — не играет роли).

неограниченный буфер printf — отформатированный перенос непосредственно в поток

я понимаю, что большинство реализаций printf полагаются на что-то вроде

чтобы фактически обрабатывать форматирование, а затем они выводят их в поток через puts.

Есть ли какая-либо реализация, которая минимизирует размер _acBuffer [0], требуемый при сохранении возможности печати всей строки?

Очевидно, что-то вроде:

Ваши мысли очень ценятся!

Ваше понимание просто неверно. Я никогда не видел и не слышал о реализации printf которая работает, сначала форматируя весь вывод во временном буфере строк. Обычно printf выполняется наоборот: фундаментальным строительным блоком является vfprintf а vsnprintf — это оболочка для того, что создает фальшивый FILE , буфер которого является целевой строкой.

Редактирование. Некоторые популярные (например, glibc) реализации действительно используют неограниченные большие промежуточные буферы для определенных форматов, особенно широкие преобразования символов, и непредсказуемы, если они не могут выделить достаточную память для буфера. Однако это проблема с некачественной реализацией; нет основополагающей причины, при которой любая из функций printf должна требовать больше, чем небольшое постоянное количество рабочего пространства, независимо от того, что они печатают.

Я бы сказал, что вся спецификация fprintf (или printf ) является тем, что она позволяет «безбумажную» однопроходную реализацию этой функции. Т.е. он преобразует данные последовательно по частям (если требуется преобразование), немедленно отправляет их на выход и забывает об этом навсегда. Функция может использовать промежуточный буфер для преобразования числовых данных, но это временный буфер фиксированного и незначительного размера времени компиляции.

Если я что-то не упустил, правильно реализованная функция fprintf должна налагать абсолютно никаких ограничений на продолжительность результирующей строки. Ваша гипотетическая реализация через vsnprintf нарушит этот принцип.

Разница между fprintf, printf и sprintf?

Может ли кто-нибудь объяснить простым языком о различиях между printf , fprintf и sprintf с примерами?

В каком потоке это?

Я действительно запутался между тремя из них, читая о «Обработка файлов в C».

В C «поток» является абстракцией; с точки зрения программы это просто производитель (входной поток) или потребитель (выходной поток) байтов. Он может соответствовать файлу на диске, каналу, вашему терминалу или другому устройству, например принтеру или tty. Тип FILE содержит информацию о потоке. Обычно вы не связываетесь непосредственно с содержимым объекта FILE , вы просто передаете указатель на него различным подпрограммам ввода/вывода.

Существует три стандартных потока: stdin — указатель на стандартный поток ввода, stdout — указатель на стандартный поток вывода, а stderr — указатель на стандартный поток вывода ошибок. В интерактивном сеансе эти три обычно относятся к вашей консоли, хотя вы можете перенаправить их, чтобы указать на другие файлы или устройства:

В этом примере stdin теперь указывает на inputfile.dat , stdout указывает на output.txt , а stderr указывает на errors.txt .

fprintf записывает форматированный текст в указанный вами выходной поток.

printf эквивалентен записи fprintf(stdout, . ) и записывает форматированный текст туда, куда в данный момент указывает стандартный поток вывода.

sprintf записывает форматированный текст в массив char , а не в поток.

printf выводит в стандартный поток вывода ( stdout )

fprintf идет в дескриптор файла ( FILE* )

sprintf отправляется в выделенный вами буфер. ( char* )

Преобразование регистратора в стиле printf с буфером стека в поток строк

В настоящее время у меня есть решение для ведения журнала, которое определяет макрос как это:

Параметр сообщения const char * использует формат, похожий на printf, например: « Меня зовут% s, а я% u ».

Фактический метод ведения журнала, который я использую, заключается в том, что я объявляю переменную char buffer [2048] в стеке и использую vsnprintf для преобразования моего параметра va_list, как определено в моем параметре сообщения, в мой буфер. Тем не менее, поскольку я часто обрабатываю значения, которые могут быть значительно больше, чем 2 КБ, мой буфер всегда усекается, поэтому я подумал о том, чтобы переделать весь процесс вокруг stringstream, который был бы более гибким.

При реализации этого решения я столкнулся с проблемой при извлечении моих параметров va_args из моего списка для использования с оператором c++ c logging variadic-functions

1 ответ

Я бы посоветовал не пытаться переопределить vsnprintf поверх stringstream. Если вы намереваетесь оставить внешний интерфейс вашего регистратора без изменений, вы должны оставить vsnprintf!

Во-первых, вы должны угадать окончательный размер отформатированной строки и выделить некоторый буфер (используя C ++ 11 unique_ptr, если вам это нравится). Вы не должны бояться накладных расходов при распределении, так как это в основном то, что stringstring будет делать под капотом.

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

Наконец, сделайте все, что вам нужно сделать с этой форматированной строкой: — создайте std :: string — запишите ее куда-нибудь (в файл?) — просто не забудьте освободить память (или используйте unique_ptr).

Примечание: не используйте std :: string в качестве буфера напрямую. Запись в буфер, возвращаемый функцией std :: string :: c_str (), является неопределенным поведением.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL