Как использовать строки форматирования в пользовательских функциях? - PullRequest
0 голосов
/ 26 февраля 2019

Я хочу написать функцию для печати символов на ЖК-дисплее аналогично тому, как printf / sprintf использует форматирование строк.

Ответы [ 4 ]

0 голосов
/ 28 февраля 2019

Этот ответ берет лучшие части всех других ответов и объединяет их в один.Я считаю, что это лучший способ сделать это, учитывая все факторы, и объясню более подробно после представления примера.

Резюме:

Вот полный пример, в том числе с базовой проверкой ошибокв функции.Здесь я создаю 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() фактически создает отформатированную строку.Для не приложений реального времени, которые также имеют большие объемы ОЗУ (например, приложения для ПК), это идеальный подход.Тем не менее, для микроконтроллеров я настоятельно рекомендую против этого, потому что:

  1. Это недетерминировано.Для вызова free() может потребоваться различное (и неопределенное предварительное) количество времени для завершения при каждом вызове.Это потому, что куча памяти становится фрагментированной со временем.Это означает, что этот подход не подходит для систем реального времени.
    • Для получения дополнительной информации о различных реализациях кучи для malloc() и free(), просмотрите 5 реализаций кучи, например, описанных FreeRTOS здесь: https://www.freertos.org/a00111.html. Поиск на этой странице для «детерминированности».
  2. Неограниченно.Он попытается 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 ++? .

Итак, собрав все вышеперечисленное, вы получите идеальное решение для микроконтроллеров, котороеЯ представил выше.

0 голосов
/ 26 февраля 2019

Вы можете написать переменную функцию и передать параметры в vsnprintf():

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

void display(int foo, int bar, char const *format, ...)
{
    va_list arglist;
    va_start(arglist, format);

    int length = vsnprintf(NULL, 0, format, arglist);
    char *buffer = malloc(length * sizeof *buffer); 
    vsnprintf(buffer, length, format, arglist);
    va_end(arglist);

    puts(buffer);
    free(buffer);
}

int main(void)
{
    display(42, 13, "%s %d %f", "Hello", 99, 100.13);
}
0 голосов
/ 27 февраля 2019

, поскольку наиболее часто используемым компилятором arm является gcc, я остановлюсь только на этом.Компилятор может проверить формат и параметры так же, как это делает printf

__attribute__ ((format (printf...

Из документации gcc

формат (архетип, строковый индекс, первый-to-check) Атрибут format указывает, что функция принимает аргументы стиля printf, scanf, strftime или strfmon, которые должны проверяться типом по строке формата.Например, объявление:

          extern int
          my_printf (void *my_object, const char *my_format, ...)
                __attribute__ ((format (printf, 2, 3)));


causes the compiler to check the arguments in calls to my_printf for consistency with the printf style format string argument

my_format.

The parameter archetype determines how the format string is interpreted, and should be printf, scanf, strftime or strfmon. (You

также может использовать printf , scanf , strftime или strfmon .) Параметр string-index указывает, какой аргумент является аргументом строки формата (начиная с 1), в то время как first-to-check - это номер первого аргумента, который проверяется по строке формата,Для функций, где аргументы недоступны для проверки (например, vprintf), укажите третий параметр как ноль.В этом случае компилятор проверяет только строку формата на согласованность.Для форматов strftime третий параметр должен быть равен нулю.

In the example above, the format string (my_format) is the second argument of the function my_print, and the arguments to check start

с третьим аргументом, поэтому правильными параметрами для атрибута формата являются 2 и 3.

The format attribute allows you to identify your own functions which take format strings as arguments, so that GCC can check the

вызовык этим функциям за ошибки.Компилятор всегда (если не используется -ffreestanding) проверяет форматы стандартных функций библиотеки printf, fprintf, sprintf, scanf, fscanf, sscanf, strftime, vprintf, vfprintf и vsprintf всякий раз, когда запрашиваются такие предупреждения (с использованием -Wformat), поэтому существуетнет необходимости изменять заголовочный файл stdio.h.В режиме C99 также проверяются функции snprintf, vsnprintf, vscanf, vfscanf и vsscanf.За исключением строго соответствующих стандартных режимов C, также проверяется функция X / Open strfmon, как printf_unlocked и fprintf_unlocked.См. Опции Контроля C Диалект.format_arg (string-index) Атрибут format_arg указывает, что функция берет строку формата для функции стиля printf, scanf, strftime или strfmon и изменяет ее (например, чтобы перевести ее на другой язык), чтобы результат можно было передатьфункция стиля printf, scanf, strftime или strfmon (с остальными аргументами функции форматирования, такими же, как они были бы для неизмененной строки).Например, объявление:

          extern char *
          my_dgettext (char *my_domain, const char *my_format)
                __attribute__ ((format_arg (2)));


causes the compiler to check the arguments in calls to a printf, scanf, strftime or strfmon type function, whose format string argument

является вызовом функции my_dgettext для согласования с аргументом строки формата my_format.Если атрибут format_arg не был указан, то при таких вызовах функций форматирования компилятор мог бы сказать, что аргумент строки формата не является постоянным;это вызовет предупреждение при использовании -Wformat-nonliteral, но вызовы не могут быть проверены без атрибута.

0 голосов
/ 26 февраля 2019

Вы можете использовать функцию sprintf для форматирования строк и печати на ЖК-дисплее.

 char buffer[50]; 
 int a = 10, b = 20, c; 
 c = a + b; 
 sprintf(buffer, "Sum of %d and %d is %d", a, b, c); 

Теперь buffer будет иметь отформатированные строки

...