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

问题描述 投票:1回答:4

我想写一个函数来在LCD上打印字符,就像printf / sprintf使用格式化字符串一样。

c printf stm32 lcd
4个回答
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
投票

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

1
投票

这个答案采用了所有其他答案的最佳部分,并将它们合二为一。考虑到所有因素,我认为这是最佳方式,并在演示示例后将更详细地解释。

Summary:

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

Example Code:

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;
}

Explanation & comparison to alternate approaches:

@Harikrishnan points out you should use sprintf()。这是正确的轨道,是一种有效但不太通用和完整的方法。使用variadic function创建一个新的vsnprintf(),就像@Swordfish和我所做的那样,更好。

@Swordfish做fantastic demonstration正确使用vsnprintf(),以创建自己的printf()variadic function。他的例子(除了缺乏错误处理)是一个完美的模板,用于依赖于动态内存分配的自定义printf()式实现。他第一次调用vsnprintf(),使用NULL目标缓冲区,只能确定他需要为格式化字符串分配多少字节(这是这个应用程序的巧妙和常用技巧),而他第二次调用vsnprintf()创建格式化的字符串。对于也具有大量RAM的非实时应用程序(例如:PC应用程序),这是一种完美的方法。但是,对于微控制器,我强烈建议不要使用它,因为:

  1. 这是不确定的。调用free()可以在每次调用时使用不同的(并且不确定的前手)时间量。这是因为堆内存随着时间的推移而变得碎片化。这意味着这种方法对实时系统不利。 有关malloc()free()的各种堆实现的更多信息,请查看5个堆实现,例如FreeRTOS在这里描述的:https://www.freertos.org/a00111.html。在此页面中搜索“deterministic”。
  2. 它是无限的。它将尝试malloc()格式化字符串所需的任何内存量。这很糟糕,因为它更容易出现堆栈溢出。在基于安全的微控制器系统上,需要强烈防止堆栈溢出。一种优选的方法是使用静态分配的内存,就像我所做的那样,具有固定的最大大小。

此外,它缺少GCC“格式”属性,这是一个很好的触摸(更多关于此在下面)。

@P__J__ mentions GCC“格式”属性。我的例子也使用了它。

如果使用GCC编译器或类似的任何其他编译器,强烈建议添加到您所做的任何自定义printf()函数。

GCC documentation在名为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-check赋值时,此类方法的参数应从两个而不是一个计算。

换句话说,以下是应用于类似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)

在这里阅读更多我的其他答案:How should I properly use __attribute__ ((format (printf, x, y))) inside a class method in C++?

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


0
投票

因为最常用的arm编译器是gcc,我只关注这个。编译器可以检查与printf相同的格式和参数

__attribute__ ((format (printf...

来自gcc文档

format(archetype,string-index,first-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

使用第三个参数,因此format属性的正确参数是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时生成警告,但是如果没有该属性则无法检查调用。

© www.soinside.com 2019 - 2024. All rights reserved.