In defense of printf

Folks seem to enjoy pointing out that I use printf in many of my examples of “modern C++”, as if printf is not really proper C++. Apparently, I should be using cout. The trouble is that cout isn’t exactly modern either. It has been around for as long as I can remember and it certainly doesn’t exemplify modern C++ as envisioned by C++11 and beyond. The oldest C++ textbook on my shelf was printed in 1993 and covers cout. I even posed the question to some C++ historians and they were able to date it back as far as 1989. Therefore, the argument that printf is old and cout is modern doesn’t fly. A truly modern C++ solution would also not be substantially slower than hand-written code. Most printf implementations today provide adequate type checking both at compile time and run time. Visual C++ even provides secure versions that make it quite straightforward to write defensive code quite easily with printf. Go ahead and use cout if you prefer, but don’t claim it’s the modern replacement for printf.

Here’s a slide from my 10 Practical Techniques to Power Your Visual C++ Apps course where I examine the performance of searching and sorting text. I won’t explain the numbers – you can watch the course for that – but it should be evident that cout has a serious performance problem. That analysis was done before James McNellis added some awesome modernization and performance improvements to printf in Visual C++ 2015.

cout

24 thoughts on “In defense of printf

  1. Jesse

    Not entirely germane to the OP’s point, but fiddling with std::ios_base::sync_with_stdio can have an (implementation-defined) improving effect on iostream. Probably still not to printf()-level, though.

    Reply
  2. FJW

    I just did a few measurements on my system (archlinux x64) with the latest version of clang, libstdc++ and libc++. Just a microbenchmark to do this:

    void print_stuff_cout(const std::vector& strings, const std::vector& ints) {
    assert(strings.size() == ints.size());
    const auto n = strings.size();
    for(std::size_t i=0; i <n; ++i) {
    std::cout << strings[i] << ", " << ints[i] << std::endl;
    // alternatively:
    // std::printf("%s, %u\n", strings[i].c_str(), ints[i]);
    }
    }

    both vectors contained 5000 elements, where the strings where created like this:

    auto make_strings(std::size_t n) {
    auto strings = std::vector{};
    strings.reserve(n);
    for(std::size_t i=0; i < n; ++i) {
    strings.emplace_back(i, 'a');
    }
    return strings;
    }

    I think this is a relatively fair and realistic use-case; the results where however interesting:

    (I piped the output of the program to /dev/null in order to measure the actual routines instead of X11.)

    Just using std::cout and using std::cout after calling std::cout.sync_with_stdio(false) were quite
    comparable over several runs (the bulk was at 2.8e6/2.4e6 ticks of std::high_resolution_clock). OTOH std::printf had it's bulk at about 1e7 ticks, so about four times slower than the “terribly slow std::cout”. This was with libstdc++, but libc++ behaved basically exactly the same.

    I guess it is just microsoft having a ridiculously bad implementation of streams, but there doesn't seem to be anything in principle that makes streams slow.

    Reply
  3. S. Colcord

    “I like printf because cout is slow” is not really useful (and not surprising to people who’ve had to mess with iostreams much). This article would be much more useful if it:

    1) Included a discussion of printf/cout tradeoffs, and
    2) Compared the internals of each to explain the cause of the speed difference.

    Reply
    1. martianpackets

      Ok my friend then fill us in with your own discussion of cost vs printf and draw your conclusion. Lead by example. Grass-hoppAH! 😉

      To be blunt… I become quite annoyed when I am searching for information after a long day of coding. .. just wanna solve my last code puzzle for the day and go home feeling a sense of accomplishment and knowing I justified the check that will cover kid’s braces, martial arts, and my own fat auto insurance premium. .. and I have to comb through all of this useless bantering critiquing someone’s effort to contribute to the global knowledge base rather than making their own contributions.

      I really wish people would keep subjective opinion – only posts out of how – to articles.

      Reply
  4. Santa Claus in India

    About the only advantage that I feel cout has over printf is that the fancy formatting specifiers for the output are closer to the variable, so if you are outputting whole lot of stuff, you can indent-align such that you can keep single-line each for all related things. Easier to hunt down in case if you wanna change anything.

    For printf, I actually stick in the actual outputted string as comment on top of the code
    and indent all printf-data-supply-variables in groups of four.

    I have been programming in C++ since 1988 and I learnt cout as replacement of printf back then. In those days, template programming did make it look intimidating complicated.

    Reply
  5. Helmut Zeisel

    Some interesting remarks from Bjarne Stroustrup on the history (and efficiency) of iostreams (and cout) can be found in the following interview from 2001:

    http://www.stroustrup.com/01chinese.html

    C++ View: Jerry Schwarz reviewed the history of IOStream in the preface of the book Standard C++ IOStream and Locales. I guess that there must be many interesting stories in the process of transiting from classic stream into the standard IOStream. Can you tell us some?

    Bjarne Stroustrup: I do not want to try to add to Jerry’s description of the transition from my streams to the current iostreams. Instead, I’d like to emphasize that the original streams library was a very simple and very efficient library. I designed and built it in a couple of months.

    The key decisions was to separate formatting from buffering, and to use the type-safe expression syntax (relying on operators <>). I made these decisions after discussions with my colleague Doug McIlroy at AT&T Bell Labs. I chose <> after experiments showed alternatives, such as , comma, and = not to work well. The type safety allowed compile-time resolution of some things that C-style libraries resolve at run-time, thus giving excellent performance. Very soon after I started to use streams, Dave Presotto transparently replaced the whole buffering part of my implementation with a better one. I didn’t even notice he’d done that until he later told me!

    The current iostreams library will never be small, but I believe that aggressive optimization techniques will allow us to regain the efficiency of the original in the many common cases where the full generality of iostreams is not used. Note that much of the complexity in iostreams exist to serve needs that my original iostreams didn’t address. For example, standard iostreams with locales can handle Chinese characters and strings in ways that are beyond the scope of my original streams.

    Reply
  6. sam@foobar.com

    Hmm, no-one claims that cout is modern, but printf is ancient. However, that’s not to be the point (old could be gold). The real issue is with the comparison – Microsoft C++ is ancient and inefficient. Try the experiment on GCC.

    Reply
  7. Sebastian Redl

    I’ve never seen anyone propose cout over printf for being “modern”. I’ve seen it promoted for being type-safe and extensible.

    Reply
  8. vincent

    In defence of printf – unlike cout and cin streaming which can be changed with global modifiers – what you see is generally what you get – I seriously despise STL – it would have be much better if ‘string’ had have been recognised by the compiler as a first class type and not an arbitrary library type using peculiar operator overloading to implement it. But I suppose we must forgive BS for his foibles.

    Reply
  9. David

    On a slight aside using length = snprintf is much more fraught with dangers and length should always be sanity checked.

    Reply
  10. Oscar Romero

    Hi Kenny Kerr, let me say to you something: Graphic about “Indexing” & “Output” above lacks of “names” both horizontally. Defense of printf need take care of details.
    Greetings!

    Reply
  11. shintakezou

    to me it’s not only a matter of being modern or not. cout is not comparable at all with printf “paradigm”, which we find in other language – like, say, having placeholders in the string and then binding someway variables to show their values. There’s a big difference between printf(“Take %-10s %03d times and for %.2lf minutes, then %16s with %-8s”, object, n, minutes, sentence, instrument); and std::cout << "Take " << object << " " << n << " times and for " << minutes << " minutes, then " << sentence << " with " << instrument; … and I've avoided intentionally formatting (not to mention translation and parameter positions…)… not to be rude, but std::cout s*cks at this kind of thing – though it could more "powerful" in other circumstances.
    Thus, I totally agree and printf does make sense even in C++ "modern" code – unless there's a C++ish way of achieving similar aim with comparable ease. (Afaik, you need to bake your own cookies since standard c++ classes miss such a thing).

    Reply
  12. Mat

    I’ve always found the text formats used in printf far more readable than a strings and variables concatenated together. That is, I can instantly see the relationship between fixed and variable text in the final output string. Same goes for string.Format verse concatenation in .NET.

    Reply
  13. Antonio

    It’s alright if you don’t exlpain the graphs, and just refer to the source, but if you post them at least include what the units are and what is being measured…

    Reply
  14. daf

    In addition, cout/cin/fout/fin are horribly not threadsafe. Not just in the expected manner (that two threads using, say, fout() on the same descriptor would clash — that would be totally acceptable). I’m talking about concurrent cout and fout calls mixing data between stdout and your file target; ie stuff meant for the console ends up in the file and/or vice versa. I saw this in existing production code when I was still quite new to C++ and couldn’t believe the stupidity of it. The specs don’t guarantee thread-safety and the implementation at hand (GNU) used a shared memory buffer for both.
    So, yeah, printf, please. And fwrite() (:

    Reply
  15. duskoKOscica

    In way it still has its place, but there is that big bad string that could rip the fabrick of your address space!

    Don’t have enough knowledge to elaborate to this topic!

    Reply
  16. Charles

    Another of those “if it is faster you must use it” stories. Please don’t use printf and certainly not sprintf in C++ programs. It has nothing to do with “modern” or any of that nonsense. Anyone who has ever done this should know why:

    printf(“%d\n”, 3.8);

    printf is not type safe. It has no way to ensure that the argument matches the format. This is a common source of bugs in C programs. The C++ streams, while by no means perfect, are type safe. If you do:

    cout << x;

    If you change the type of x, it will either work or the compiler will complain if the type is not supported. It is also easy to add new types, something you can't do with printf at all.

    printf does not support the C++ string types unless you use c_str(). And, you should be using those types, not a lot of null-terminated strings.

    In general, I would hope you are not using printf just because it's for 8-bit characters. Unicode, anyone?

    The C++ streams system is a general system. It can be used for files, output devices, and anything else you might want to use it for. It is much more flexible.

    Reply
  17. alexms

    Old or ancient or both or none – that’s not an argument at all. Whatever works best…
    Not only every implementation of cout.operator<< I know of uses sprintf_s (or its equivalent in non-MSVC case) internally, which makes it impossible for cout to be any better than printf, that's not the core problem at all, bronze medal at best.
    The second worst thing, silver medal, about cout – and all string operations in C++ in general is that they (internally) use malloc, which is slow "as is" – malloc (or any heap allocation routine) goes thru a linked list of memory segments to find the first big enough.
    The worst thing and gold medal goes to heap locking, that malloc has to use to run that linked-list search. It is not about being slow per se, MSDN states that "There is a small performance cost to serialization"; the danger is in that this lock is app-wide, unless you create your own private heaps, so all memory allocations – maybe in a different thread, maybe 1000s of lines of code away, – are potentially influenced, and in an unpredictable manner, b/c the time spent inside the lock depends on the size of memory required.
    This often is the reason of debug vs release build behavior differences and may make debugging guess-based, not just hard but all but impossible.
    Just out of interest I counted the number of calls to _RtlEnterCriticalSection@4 from within ntdll.dll!_RtlDebugAllocateHeap@12() in just one line of code: std::cout << L"var value=" << 42;. For a release 32-bit build the number is 33. Thirty three (!) cases to change your app's behavior, if var happens to be not 42 but 987654321.
    Stack-based allocations, call them old, ancient, outdated, you name it, along the lines of char buf[]; sprintf_s(buf, ); OutputDebugStringA(buf); are free from problems 2 and 3.

    Reply
  18. Joe

    The argument that something being modern makes it better is foolish. Anyone making that argument isn’t a real developer, and has no idea what goes on under the hood. Like you pointed out, real benchmarks such as latency and i/o or memory bandwidth consumed are real justification.

    Reply
  19. duskoKoscica

    Ok!

    Lets get serious!

    cout you would use in phase of testing, so it is useful in situations that hobby programer uses, or some serious programmer when he tests his code, now days you go into graphical user interface and then it is, well guys different!

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s