如何在用户定义的函数中使用格式字符串?

时间:2019-02-26 05:16:21

标签: c printf stm32 lcd

我想编写一个函数,以类似于printf / sprintf使用格式字符串的方式在LCD上打印字符。

4 个答案:

答案 0 :(得分:1)

您可以使用sprintf函数格式化字符串并打印到LCD。

 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将具有格式化的字符串

答案 1 :(得分:1)

您可以编写一个可变参数函数并将参数传递给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);
}

答案 2 :(得分:1)

此答案吸收了所有其他答案中的最佳部分,并将它们合而为一。考虑到所有因素,我认为这是执行此操作的最佳方法,并且在介绍示例后将对其进行详细说明。

摘要:

这是一个完整的示例,其中包括函数中的基本错误检查。在这里,我创建了一个类似printf的函数lcd_printf(),其功能与printf()完全一样。它使用vsnprintf()将格式化的字符串存储到静态分配的缓冲区中。然后,您可以将该缓冲区发送到我的注释所指示的位置的LCD显示屏上。

示例代码:

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 points out you should use sprintf()。这是正确的做法,是一种有效的方法,但用途广泛且不够完整。像我和@Swordfish一样,创建一个使用vsnprintf()的新variadic function更好。

@Swordfish对vsnprintf()的正确用法进行fantastic demonstration,以便创建自己的类似printf()的{​​{3}}。他的示例(除了缺少错误处理之外)是依赖于动态内存分配的自定义printf()类实现的完美模板。他第一次使用vsnprintf()目标缓冲区对NULL进行调用,无非决定了他需要为格式化的字符串分配多少字节(这是此应用程序的一种巧妙且常用的技巧) ,他对vsnprintf()的第二次调用实际上创建了格式化的字符串。对于还具有大量RAM的实时应用程序(例如PC应用程序),这是一种完美的方法。但是,对于微控制器,我强烈建议您这样做,因为:

  1. 这是不确定的。每次调用free()都会花费不同的时间(并且事先无法确定)。这是因为堆内存随着时间的推移而变得碎片化。这意味着这种方法不适用于实时系统。
    • 有关malloc()free()的各种堆实现的更多信息,请查看5种堆实现,例如,FreeRTOS在这里介绍variadic function。在此页面上搜索“确定性”。
  2. 它是无界的。它将尝试malloc()格式化字符串所需的任何内存量。这很不好,因为它更容易发生堆栈溢出。在基于安全性至关重要的基于微控制器的系统上,必须严格防止堆栈溢出。一种首选的方法是像我一样使用静态分配的内存,并使用固定的最大大小。

此外,它缺少GCC的“格式”属性,这很不错(请参见下文)。

@P__J__ https://www.freertos.org/a00111.html GCC“格式”属性。我的示例也使用了此方法。

如果使用GCC编译器或具有类似功能的任何其他编译器,强烈建议将此方法添加到您创建的任何自定义printf()类似函数中。

mentions在名为format (archetype, string-index, first-to-check)的部分下指出:

  

format属性指定一个函数使用printf,scanf,strftime或strfmon样式参数,这些参数应根据格式字符串进行类型检查。

换句话说,它在编译时为您的自定义printf()类函数提供了额外的保护和检查。很好

对于我们的情况,只需将printf用作archetype,并为string-indexfirst-to-check参数使用数字。

  

参数 string-index 指定哪个参数是格式字符串参数(从1开始),而 first-to-check 是第一个参数的编号检查格式字符串。

     

由于非静态C ++方法具有隐式this参数,因此当为 string-index first-to赋值时,此类方法的参数应从两个而不是一个开始计数-检查

换句话说,这是应用于printf()类函数原型的此属性的一些有效示例用法:

  • 在C中:

    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)
    

在我的其他答案中了解更多信息:GCC documentation

因此,将以上所有内容组合在一起,您将获得上面已介绍的微控制器的理想解决方案。

答案 3 :(得分:0)

因为最常用的arm编译器是gcc,所以我只关注这一点。编译器可以像printf一样检查格式和参数

__attribute__ ((format (printf...

来自gcc文档

  

格式(原型,字符串索引,首次检查)       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(字符串索引)       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属性不是   指定,所有编译器都可以通过这种格式调用   函数将是format字符串参数不是常量;   使用-Wformat-nonliteral时,这会产生警告,但是   没有该属性,无法检查呼叫。

相关问题