簡體   English   中英

sprintf 或 printf 的最小實現

[英]Minimal implementation of sprintf or printf

我正在研究速度至關重要且內存非常短的嵌入式 DSP。

目前,在我的代碼中,sprintf 使用的資源最多。 我只用它來格式化一些簡單的文本: %d, %e, %f, %s ,沒有任何精確或奇異的操作。

如何實現更適合我的使用的基本 sprintf 或 printf 函數?

這個假設存在將 int 轉換為字符表示的itoa ,以及將字符串寫出到任何你想要的地方的fputs

浮點輸出至少在一個方面不符合標准:它不會按照標准要求正確舍入,因此如果您有(例如)內部存儲為1.2399999774的值1.234 ,它將打印為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

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

但是,它實現了說明符csdux和非標准的bm (二進制和內存十六進制轉儲); 還有標志0 , 1-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

tl;dr :考慮更小但更完整的sprintf()實現。

您可能正在使用的標准庫的sprintf()實現可能會占用大量資源。 但是您可能可以利用獨立的sprintf()實現,您將獲得更完整的功能,而無需支付如此多的內存使用費用。

現在,如果您告訴我們您只需要一些基本功能,您為什么會選擇它? 因為(s)printf()使用的本質是我們在進行過程中傾向於使用它的更多方面。 你注意到你想打印更大的數字,或者遠十進制數字的差異; 你想打印一堆值,然后決定你想讓它們對齊。 或者其他人想使用您添加的打印功能來打印您沒有想到的東西。

我面臨着類似的需求,盡管不是因為資源限制,而是因為標准庫不可用……我最終選擇了一個名為 Marco Paland 的人的獨立實現,專為低資源使用而設計,並且我正在修復它,因為它有一堆錯誤(你不會相信在sprintf()錯過極端情況是多么容易)。 存儲庫在這里 對於帶有一些附加功能(例如二進制值;但不包括%a說明符)的幾乎完整的 C99 sprintf 實現,它大約為 700 LoC。 如果你願意,你可以 ifdef 一些功能 - 有一些定義的控制。

我編寫 nanoprintf 是為了在微小的二進制大小和良好的特征覆蓋之間找到平衡。 截至今天,“基本”配置是 < 1000 字節的二進制代碼,包括浮點解析在內的“最大”配置是 ~2600 字節。 100% C99 代碼,無外部依賴,一個頭文件。

https://github.com/charlesnicholson/nanoprintf

我還沒有看到比這更小的 vsnprintf 實現,它具有類似的功能集。 我還在公共領域發布了該軟件,因此它完全不受阻礙。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM