【问题标题】:Minimal implementation of sprintf or printfsprintf 或 printf 的最小实现
【发布时间】:2013-05-14 21:05:26
【问题描述】:

我正在开发一个嵌入式 DSP,其中速度至关重要,而且内存非常短。

目前,sprintf 在我的代码中使用了所有函数中最多的资源。我只用它来格式化一些简单的文本:%d, %e, %f, %s,没有任何精确或奇异的操作。

如何实现更适合我使用的基本 sprintf 或 printf 函数?

【问题讨论】:

  • 在 google 中输入你的主题标题在这里找到了很多点击。顺便说一句:你总是可以自己动手。 %e 和 %f 可能有点难,但 %d 和 %s 是微不足道的。
  • 继@wildplasser 评论之后,你真的必须使用浮点数吗?
  • 是的,我愿意,DSP 用于提供通过 ADC 测量的值。所以需要浮点数。
  • 嗯,ADC 通常会生成无符号整数?
  • 如果在fixed point arithmetic中执行计算,则不需要浮点数

标签: c embedded printf


【解决方案1】:

这个假设存在一个 itoa 来将 int 转换为字符表示,以及一个 fputs 来写出一个字符串到任何你想去的地方。

浮点输出至少在一个方面是不合格的:它没有尝试按照标准要求正确舍入,所以如果你有(例如)一个值1.234,它在内部存储为1.2399999774,它将被打印为1.2399 而不是1.2340。这样可以节省大量工作,并且对于大多数典型用途来说仍然足够。

除了您询问的转换之外,这还支持%c%x,但是如果您想摆脱它们,删除它们非常简单(这样做显然会节省一点 内存)。

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>

static void ftoa_fixed(char *buffer, double value);
static void ftoa_sci(char *buffer, double value);

int my_vfprintf(FILE *file, char const *fmt, va_list arg) {

    int int_temp;
    char char_temp;
    char *string_temp;
    double double_temp;

    char ch;
    int length = 0;

    char buffer[512];

    while ( ch = *fmt++) {
        if ( '%' == ch ) {
            switch (ch = *fmt++) {
                /* %% - print out a single %    */
                case '%':
                    fputc('%', file);
                    length++;
                    break;

                /* %c: print out a character    */
                case 'c':
                    char_temp = va_arg(arg, int);
                    fputc(char_temp, file);
                    length++;
                    break;

                /* %s: print out a string       */
                case 's':
                    string_temp = va_arg(arg, char *);
                    fputs(string_temp, file);
                    length += strlen(string_temp);
                    break;

                /* %d: print out an int         */
                case 'd':
                    int_temp = va_arg(arg, int);
                    itoa(int_temp, buffer, 10);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;

                /* %x: print out an int in hex  */
                case 'x':
                    int_temp = va_arg(arg, int);
                    itoa(int_temp, buffer, 16);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;

                case 'f':
                    double_temp = va_arg(arg, double);
                    ftoa_fixed(buffer, double_temp);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;

                case 'e':
                    double_temp = va_arg(arg, double);
                    ftoa_sci(buffer, double_temp);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;
            }
        }
        else {
            putc(ch, file);
            length++;
        }
    }
    return length;
}

int normalize(double *val) {
    int exponent = 0;
    double value = *val;

    while (value >= 1.0) {
        value /= 10.0;
        ++exponent;
    }

    while (value < 0.1) {
        value *= 10.0;
        --exponent;
    }
    *val = value;
    return exponent;
}

static void ftoa_fixed(char *buffer, double value) {  
    /* carry out a fixed conversion of a double value to a string, with a precision of 5 decimal digits. 
     * Values with absolute values less than 0.000001 are rounded to 0.0
     * Note: this blindly assumes that the buffer will be large enough to hold the largest possible result.
     * The largest value we expect is an IEEE 754 double precision real, with maximum magnitude of approximately
     * e+308. The C standard requires an implementation to allow a single conversion to produce up to 512 
     * characters, so that's what we really expect as the buffer size.     
     */

    int exponent = 0;
    int places = 0;
    static const int width = 4;

    if (value == 0.0) {
        buffer[0] = '0';
        buffer[1] = '\0';
        return;
    }         

    if (value < 0.0) {
        *buffer++ = '-';
        value = -value;
    }

    exponent = normalize(&value);

    while (exponent > 0) {
        int digit = value * 10;
        *buffer++ = digit + '0';
        value = value * 10 - digit;
        ++places;
        --exponent;
    }

    if (places == 0)
        *buffer++ = '0';

    *buffer++ = '.';

    while (exponent < 0 && places < width) {
        *buffer++ = '0';
        --exponent;
        ++places;
    }

    while (places < width) {
        int digit = value * 10.0;
        *buffer++ = digit + '0';
        value = value * 10.0 - digit;
        ++places;
    }
    *buffer = '\0';
}

void ftoa_sci(char *buffer, double value) {
    int exponent = 0;
    int places = 0;
    static const int width = 4;

    if (value == 0.0) {
        buffer[0] = '0';
        buffer[1] = '\0';
        return;
    }

    if (value < 0.0) {
        *buffer++ = '-';
        value = -value;
    }

    exponent = normalize(&value);

    int digit = value * 10.0;
    *buffer++ = digit + '0';
    value = value * 10.0 - digit;
    --exponent;

    *buffer++ = '.';

    for (int i = 0; i < width; i++) {
        int digit = value * 10.0;
        *buffer++ = digit + '0';
        value = value * 10.0 - digit;
    }

    *buffer++ = 'e';
    itoa(exponent, buffer, 10);
}

int my_printf(char const *fmt, ...) {
    va_list arg;
    int length;

    va_start(arg, fmt);
    length = my_vfprintf(stdout, fmt, arg);
    va_end(arg);
    return length;
}

int my_fprintf(FILE *file, char const *fmt, ...) {
    va_list arg;
    int length;

    va_start(arg, fmt);
    length = my_vfprintf(file, fmt, arg);
    va_end(arg);
    return length;
}


#ifdef TEST 

int main() {

    float floats[] = { 0.0, 1.234e-10, 1.234e+10, -1.234e-10, -1.234e-10 };

    my_printf("%s, %d, %x\n", "Some string", 1, 0x1234);

    for (int i = 0; i < sizeof(floats) / sizeof(floats[0]); i++)
        my_printf("%f, %e\n", floats[i], floats[i]);

    return 0;
}

#endif

【讨论】:

    【解决方案2】:

    我在这里添加了我自己的(v)sprintf 实现,但它不提供浮点支持(这就是我在这里的原因......)。

    但是,它实现了说明符 csdux 以及非标准的 bm(二进制和内存 hexdump);还有标志01-9*+

    #include <stdarg.h>
    #include <stdint.h>
    
    #define min(a,b) __extension__\
        ({ __typeof__ (a) _a = (a); \
           __typeof__ (b) _b = (b); \
           _a < _b ? _a : _b; })
    
    enum flag_itoa {
        FILL_ZERO = 1,
        PUT_PLUS = 2,
        PUT_MINUS = 4,
        BASE_2 = 8,
        BASE_10 = 16,
    };
    
    static char * sitoa(char * buf, unsigned int num, int width, enum flag_itoa flags)
    {
        unsigned int base;
        if (flags & BASE_2)
            base = 2;
        else if (flags & BASE_10)
            base = 10;
        else
            base = 16;
    
        char tmp[32];
        char *p = tmp;
        do {
            int rem = num % base;
            *p++ = (rem <= 9) ? (rem + '0') : (rem + 'a' - 0xA);
        } while ((num /= base));
        width -= p - tmp;
        char fill = (flags & FILL_ZERO)? '0' : ' ';
        while (0 <= --width) {
            *(buf++) = fill;
        }
        if (flags & PUT_MINUS)
            *(buf++) = '-';
        else if (flags & PUT_PLUS)
            *(buf++) = '+';
        do
            *(buf++) = *(--p);
        while (tmp < p);
        return buf;
    }
    
    int my_vsprintf(char * buf, const char * fmt, va_list va)
    {
        char c;
        const char *save = buf;
    
        while ((c  = *fmt++)) {
            int width = 0;
            enum flag_itoa flags = 0;
            if (c != '%') {
                *(buf++) = c;
                continue;
            }
        redo_spec:
            c  = *fmt++;
            switch (c) {
            case '%':
                *(buf++) = c;
                break;
            case 'c':;
                *(buf++) = va_arg(va, int);
                break;
            case 'd':;
                int num = va_arg(va, int);
                if (num < 0) {
                    num = -num;
                    flags |= PUT_MINUS;
                }
                buf = sitoa(buf, num, width, flags | BASE_10);
                break;
            case 'u':
                buf = sitoa(buf, va_arg(va, unsigned int), width, flags | BASE_10);
                break;
            case 'x':
                buf = sitoa(buf, va_arg(va, unsigned int), width, flags);
                break;
            case 'b':
                buf = sitoa(buf, va_arg(va, unsigned int), width, flags | BASE_2);
                break;
            case 's':;
                const char *p  = va_arg(va, const char *);
                if (p) {
                    while (*p)
                        *(buf++) = *(p++);
                }
                break;
            case 'm':;
                const uint8_t *m  = va_arg(va, const uint8_t *);
                width = min(width, 64); // buffer limited to 256!
                if (m)
                    for (;;) {
                        buf = sitoa(buf, *(m++), 2, FILL_ZERO);
                        if (--width <= 0)
                            break;
                        *(buf++) = ':';
                    }
                break;
            case '0':
                if (!width)
                    flags |= FILL_ZERO;
                // fall through
            case '1'...'9':
                width = width * 10 + c - '0';
                goto redo_spec;
            case '*':
                width = va_arg(va, unsigned int);
                goto redo_spec;
            case '+':
                flags |= PUT_PLUS;
                goto redo_spec;
            case '\0':
            default:
                *(buf++) = '?';
            }
            width = 0;
        }
        *buf = '\0';
        return buf - save;
    }
    
    int my_sprintf(char * buf, const char * fmt, ...)
    {
        va_list va;
        va_start(va,fmt);
        int ret = my_vsprintf(buf, fmt, va);
        va_end(va);
        return ret;
    }
    
    #if TEST
    int main(int argc, char *argv[])
    {
        char b[256], *p = b;
        my_sprintf(b, "%x %d %b\n", 123, 123, 123);
        while (*p)
            putchar(*p++);
    }
    #endif
    

    【讨论】:

      【解决方案3】:

      tl;dr :考虑一个更小但更完整的sprintf() 实现

      https://github.com/eyalroz/printf

      您可能正在使用的标准库的sprintf() 实现可能会占用大量资源。但是,您可以利用独立的sprintf() 实现,您可以获得更完整的功能,而无需支付如此多的内存使用。

      现在,如果您告诉我们您只需要一些基本功能,为什么还要选择它?因为(s)printf() 使用的本质是我们倾向于在进行过程中使用它的更多方面。您注意到您想要打印更大的数字,或远十进制数字的差异;你想打印一堆值,然后决定你想让它们对齐。或者其他人想使用您添加的打印功能来打印您没有想到的东西。

      编辑:

      我也面临着类似的需求,尽管不是因为资源限制,而是因为标准库的不可用...我最终选择了一个由 Marco Paland 为低资源设计的独立实现使用,并修复了一些,因为它有一堆错误(你不会相信在sprintf() 中错过角落案例是多么容易)。该存储库位于我上面发布的链接中。几乎完整的 C99 sprintf 实现大约需要 850 行代码(例如二进制值;但不包括 %a 说明符)。如果您愿意,可以使用构建选项来取消对浮点的支持 - 它需要最多的代码,而有些人不需要它。

      【讨论】:

      • 嗨。你考虑过nanoprintf吗?我很好奇它与 Paland 的图书馆相比如何。
      • @AndriyMakukha:嗯,考虑到我提到的shear number of issules,现在它也有点像我的图书馆了。不过,要回答您的问题 - 不,我不知道 nanoprintf,现在来看一下。
      【解决方案4】:

      我编写 nanoprintf 是为了在微小的二进制大小和良好的特征覆盖之间找到平衡点。截至今天,“基本”配置小于 1000 字节的二进制代码,“最大”配置(包括浮点解析)约为 2600 字节。 100% C99 代码,无外部依赖,一个头文件。

      https://github.com/charlesnicholson/nanoprintf

      我还没有见过比这更小的 vsnprintf 实现,它具有类似的功能集。我还在公共领域发布了该软件,因此它完全不受阻碍。

      这是一个使用 vsnprintf 功能的示例:

      your_project_nanoprintf.c

      #define NANOPRINTF_USE_FIELD_WIDTH_FORMAT_SPECIFIERS 1
      #define NANOPRINTF_USE_PRECISION_FORMAT_SPECIFIERS 1
      #define NANOPRINTF_USE_LARGE_FORMAT_SPECIFIERS 1
      #define NANOPRINTF_USE_FLOAT_FORMAT_SPECIFIERS 1
      #define NANOPRINTF_USE_WRITEBACK_FORMAT_SPECIFIERS 0
      
      // Compile nanoprintf in this translation unit.
      #define NANOPRINTF_IMPLEMENTATION
      #include "nanoprintf.h"
      

      your_log.h

      void your_log(char const *s);
      void your_log_v(char const *fmt, ...);
      

      your_log.c

      #include "your_log.h"
      #include "nanoprintf.h"
      
      #include <stdarg.h>
      
      void your_log_v(char const *s) {
        // Do whatever you want with the fully formatted string s.
      }
      
      void your_log(char const *fmt, ...) {
          char buf[128];
      
          va_arg args;
          va_start(args, fmt);
          npf_vsnprintf(buf, sizeof(buf), fmt, args); // Use nanoprintf for formatting.
          va_end(args);
      
          your_log_write(buf);
      }
      

      Nanoprintf 还提供了一个类似 snprintf 的自定义版本,该版本采用用户提供的 putc 回调来处理诸如 UART 写入之类的事情。

      【讨论】:

        猜你喜欢
        • 2021-12-08
        • 2011-06-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-09
        • 2019-10-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多