简体   繁体   中英

cost of fprintf

I am developing an embedded application in C++ for a platform with limited code/data RAM, but rather unlimited RAM for filesystem usage.

While looking for reducing the code size, I realized that excluding fprintf() lines contributed a lot to the size of generated code.

My questions are : 1. Why is the cost of fprintf so high ? 2. If I exclude the fprintf functionality, what would be the alternative to generate log files describing the occurances through the application run ?

In embedded systems, printf can sometimes drag in all the floating point support for format strings like %f .

More intelligent environments will make the floating point options for printf an optional thing.

But even for integers, there's a lot of general purpose code in printf and you may find it's more compact to write your own routines, tailored to your specific needs, like:

outInt (char *buff, int intVal);
outChr (char *buff, char chVal);
outStr (char *buff, char *strVal);

and so on, for writing to buffers, then outBuff (char *buff) for sending it to a file or standard output.


For example, if you control the data being used (no string overflow, 16-bit twos complement integers and such), you can use the following functions:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void outChr (char *buff, char chVal) {
    *buff++ = chVal;
    *buff = '\0';
}

void outStr (char *buff, char *strVal) {
    strcpy (buff, strVal);
}

void outInt (char *buff, int intVal) {
    int divisor = 10000, printing = 0;

    // Special cases.

    if (intVal == -32768) { outStr (buff, "-32768"); return; }
    if (intVal ==      0) { outChr (buff,      '0'); return; }

    // Handle negatives.

    if (intVal < 0) { outChr (buff++, '-'); intVal = -intVal; }

    // Handle non-zero positives <= 32767.

    while (divisor > 0) {
        if ((intVal >= divisor) || printing) {
            outChr (buff++, "0123456789"[intVal/divisor]);
            printing = 1;
        }
        intVal = intVal % divisor;
        divisor /= 10;
    }
}

int main (int argc, char *argv[]) {
    char buff[1000];
    int i;
    for (i = 1; i < argc; i++) {
        outInt (buff, atoi (argv[i]));
        printf ("[%s] -> [%s]\n", argv[i], buff);
    }
    return 0;
}

Running this with:

pax$ tstprg 32767 10000 9999 10 9 1 0 -1 -9 -10 -99 -10000 -32767 -32768

outputs:

[32767] -> [32767]
[10000] -> [10000]
[9999] -> [9999]
[10] -> [10]
[9] -> [9]
[1] -> [1]
[0] -> [0]
[-1] -> [-1]
[-9] -> [-9]
[-10] -> [-10]
[-99] -> [-99]
[-10000] -> [-10000]
[-32767] -> [-32767]
[-32768] -> [-32768]

These functions should be relatively small in size since they're targeted to specific needs rather than the far more general printf family.

It takes a reasonable amount of code to provide the full ANSI compliant printf functionality.

Some embedded environments offer several different versions of printf that are considerably smaller, as they only offer selected functionality.

For example, the IAR C/C++ Compiler for MSP430 (PDF) , offers Tiny , Small , Large and Full implementations of the printf formatter, with the Tiny version only supporting the basic specifiers ( c, d, i, o, p, s, u, X, x, and % ) with no support for multibytes, floats, length modifiers, width and precision.

If your environment offers this choice then select the version of printf (and scanf ) that matches your needs and be aware of the limitations.

If your environment does not offer this choice then take a look at the various "tiny" alternative printf implementations available (such as this one from Kustaa Nyholm of SpareTimeLabs ).

I can think of three scenarios:

  1. Each time you remove a fprintf line the code size drops slightly, and when you remove the very last fprint it also drops slightly.
  2. When you remove the very last fprint the code size drops significantly.
  3. Every time you remove one instance of fprint the code size drops significantly.

In scenario 1, it isn't fprintf that is the culprit, but rather the string literals that you are passing to it. You need to get these strings out of your code, either by using very short, terse messages, or by storing the strings in a file and referencing them by some form of ID within the code (although this incurs performance hits)

In scenario 2, fprintf is (probably) the main culprit. It is quite a complex function that is capable of formatting all kinds of data types in all kinds of ways - so it takes quite a bit of code space. When you remove the last use of it the linker will eliminate it from the final binaries, making them smaller. Try using std::ofstream instead. If you only ever insert (for example) ints and strings to your output file, then only the code for handling ints and strings is linked in.

Scenario 3 is very unlikely - and would probably indicate that fprintf is being inlined wherever you use it.

Hope this helps

The answer to your first question depends on the compiler that you are using; you can only get a definitive answer by examining your compiler. As GrahamS pointed out, formatter implementation can be complicated.

Try using fputs instead of fprintf to avoid the formatter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM