Этот ответ берет лучшие части всех других ответов и объединяет их в один.Я считаю, что это лучший способ сделать это, учитывая все факторы, и объясню более подробно после представления примера.
Резюме:
Вот полный пример, в том числе с базовой проверкой ошибокв функции.Здесь я создаю printf
-подобную функцию с именем lcd_printf()
, которая работает точно так же, как printf()
.Он использует vsnprintf()
для хранения отформатированной строки в статически выделенном буфере.Затем вы можете отправить этот буфер на ЖК-дисплей в месте, указанном моим комментарием.
Пример кода:
lcd_print.h:
// For info on the gcc "format" attribute, read here under the section titled
// "format (archetype, string-index, first-to-check)":
// https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes.
int lcd_printf(const char * format, ...) __attribute__((format(printf, 1, 2)));
lcd_print.c:
#include "lcd_print.h"
#include <stdarg.h> // for variable args: va_list
#include <stdio.h> // for vsnprintf()
#include <limits.h> // for INT_MIN
// `printf`-like function to print to the LCD display.
// Returns the number of chars printed, or a negative number in the event of an error.
// Error Return codes:
// 1. INT_MIN if vsnprintf encoding error, OR
// 2. negative of the number of chars it *would have printed* had the buffer been large enough (ie: buffer would
// have needed to be the absolute value of this size + 1 for null terminator)
int lcd_printf(const char * format, ...)
{
int return_code;
// Formatted string buffer: make as long as you need it to be to hold the longest string you'd ever want
// to print + null terminator
char formatted_str[128];
va_list arglist;
va_start(arglist, format);
// Produce the formatted string; see vsnprintf documentation: http://www.cplusplus.com/reference/cstdio/vsnprintf/
int num_chars_to_print = vsnprintf(formatted_str, sizeof(formatted_str), format, arglist);
va_end(arglist);
if (num_chars_to_print < 0)
{
// Encoding error
return_code = INT_MIN;
return return_code; // exit early
}
else if (num_chars_to_print >= sizeof(formatted_str))
{
// formatted_str buffer not long enough
return_code = -num_chars_to_print;
// Do NOT return here; rather, continue and print what we can
}
else
{
// No error
return_code = num_chars_to_print;
}
// Now do whatever is required to send the formatted_str buffer to the LCD display here.
return return_code;
}
main.c:
#include "lcd_print.h"
int main(void)
{
int num1 = 7;
int num2 = -1000;
unsigned int num3 = 0x812A;
lcd_printf("my 3 numbers are %i, %i, 0x%4X\n", num1, num2, num3);
return 0;
}
Объяснение и сравнение с альтернативными подходами:
@ Harikrishnan указывает на то, что вы должны использовать sprintf()
.Это на правильном пути, и это правильный, но менее универсальный и полный подход.Лучше создать новую переменную функцию , которая использует vsnprintf()
, как я и @Swordfish, и я.
@ Swordfish делает фантастическую демонстрацию правильного использованияиз vsnprintf()
, чтобы создать собственную printf()
-подобную переменную функцию .Его пример (помимо отсутствия обработки ошибок) - идеальный шаблон для пользовательской printf()
-подобной реализации, которая опирается на динамическое выделение памяти .Его первый вызов vsnprintf()
с целевым буфером NULL
ничего не делает, кроме как определяет, сколько байтов ему нужно выделить для отформатированной строки (это гениальный и обычно используемый прием для этого приложения), и его второйвызов vsnprintf()
фактически создает отформатированную строку.Для не приложений реального времени, которые также имеют большие объемы ОЗУ (например, приложения для ПК), это идеальный подход.Тем не менее, для микроконтроллеров я настоятельно рекомендую против этого, потому что:
- Это недетерминировано.Для вызова
free()
может потребоваться различное (и неопределенное предварительное) количество времени для завершения при каждом вызове.Это потому, что куча памяти становится фрагментированной со временем.Это означает, что этот подход не подходит для систем реального времени. - Для получения дополнительной информации о различных реализациях кучи для
malloc()
и free()
, просмотрите 5 реализаций кучи, например, описанных FreeRTOS здесь: https://www.freertos.org/a00111.html. Поиск на этой странице для «детерминированности».
- Неограниченно.Он попытается
malloc()
ЛЮБОЙ объем памяти, необходимый для форматированной строки.Это плохо, так как более подвержено переполнению стека.В критически важных для безопасности системах на основе микроконтроллеров необходимо строго предотвращать переполнение стека.Предпочтительным подходом является использование статически выделенной памяти, как я сделал, с фиксированным максимальным размером.
Кроме того, в нем отсутствует атрибут «формат» GCC, что приятно (подробнее об этом ниже).
@ P__J__ упоминает атрибут "format" GCC.Мой пример также использует это.
Если вы используете компилятор GCC или любой другой компилятор, который имеет что-то похожее, настоятельно рекомендуется добавить к любой пользовательской printf()
-подобной функции, которую вы делаете.
В документации GCC в разделе под названием format (archetype, string-index, first-to-check)
указано:
Атрибут format указывает, что функция принимает printf, scanf, strftime или strfmon.аргументы стиля, которые должны проверяться типом по строке формата.
Другими словами, он обеспечивает дополнительную защиту и проверки для вашей пользовательской printf()
-подобной функции во время компиляции.Это хорошо.
В нашем случае просто используйте printf
в качестве archetype
и число для параметров string-index
и first-to-check
.
Параметр string-index указывает, какой аргумент является аргументом форматной строки (начиная с 1), тогда как first-to-check * является числомпервый аргумент для проверки по строке формата.
Поскольку нестатические методы C ++ имеют неявный этот аргумент, аргументы таких методов следует считать от двух, а не от одного, при задании значений для string-index и first-to-check .
Другими словами, вот несколько допустимых примеров использования этого атрибута, примененного к printf()
подобным прототипам функций:
InC:
int lcd_printf(const char * format, ...) __attribute__((format(printf, 1, 2))); // 1 is the format-string index (1-based), and 2 is the variadic argument (`...`) index (1-based)
int lcd_printf(my_type my_var, const char * format, ...) __attribute__((format(printf, 2, 3))); // 2 is the format-string index (1-based), and 3 is the variadic argument (`...`) index (1-based)
int lcd_printf(my_type my_var, my_type my_var2, const char * format, my_type my_var3, ...) __attribute__((format(printf, 3, 5))); // 3 is the format-string index (1-based), and 5 is the variadic argument (`...`) index (1-based)
В C ++:
int lcd_printf(const char * format, ...) __attribute__((format(printf, 2, 3))); // 2 is the format-string index (2-based), and 3 is the variadic argument (`...`) index (2-based)
int lcd_printf(my_type my_var, const char * format, ...) __attribute__((format(printf, 3, 4))); // 3 is the format-string index (2-based), and 4 is the variadic argument (`...`) index (2-based)
int lcd_printf(my_type my_var, my_type my_var2, const char * format, my_type my_var3, ...) __attribute__((format(printf, 4, 6))); // 4 is the format-string index (2-based), and 6 is the variadic argument (`...`) index (2-based)
Подробнее в моем другом ответе здесь: Как мне следует правильноиспользовать __attribute__ ((format (printf, x, y))) внутри метода класса в C ++? .
Итак, собрав все вышеперечисленное, вы получите идеальное решение для микроконтроллеров, котороеЯ представил выше.