sprintf或printf的最小实现

Gui*_*i13 18 c embedded printf

我正在研究一种速度至关重要的嵌入式DSP,而且内存很短.

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

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

Jer*_*fin 12

这个假定存在一个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
Run Code Online (Sandbox Code Playgroud)


Cha*_*son 8

我编写 nanoprintf 是为了在微小的二进制大小和良好的功能覆盖率之间找到平衡。截至目前,“基本”配置小于 800 字节的二进制代码,而包括浮点解析在内的“最大”配置小于 2500 字节。100% C99 代码,无外部依赖,一个头文件。

https://github.com/charlesnicholson/nanoprintf

我还没有见过比它更小的 vsnprintf 实现,并且具有类似的功能集。我还在公共领域发布了该软件,并使用零条款 BSD 许可证,因此它完全不受阻碍。

以下是使用 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"
Run Code Online (Sandbox Code Playgroud)

你的日志.h

void your_log(char const *s);
void your_log_v(char const *fmt, ...);
Run Code Online (Sandbox Code Playgroud)

你的日志.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);
}
Run Code Online (Sandbox Code Playgroud)

Nanoprintf 还提供类似 snprintf 和自定义版本,该版本采用用户提供的 putc 回调来执行 UART 写入等操作。