Go Ask printf() To Behave Predictably

Go Ask printf() To Behave Predictably

  • Comments 24

This is one of those problems whose solution is very evident… or bitterly hard to guess depending on how much you were influenced by what you were told about it.

I’ll paste here the case

I must be getting blind, as I can’t see what’s wrong. This code works on amd64, but crashes on x86.


  1. #include <regex>
  2. using namespace std;
  3.  
  4. int main() {
  5.     char searchMe[] = "Go ask printf() to behave predictably.";
  6.     regex rx("^Go ask ");
  7.     cmatch found;
  8.  
  9.     if (regex_search(searchMe, found, rx))
  10.         printf("Found it: \"%s\".\n", found.str());
  11.     else
  12.         printf("Didn't find it.\n");
  13. }


I debugged a bit and found out that what is failing is actually the first printf, because formatting for %s is not apparently using the address of the string, but it tries to dereference its content.

Can anyone help me see what’s wrong?

Thanks

 

You will rapidly solve it… or not! It will actually depend on what it impressed you the most about the symptoms.

  • I'm with Leo on this. The newer C++ ways are great, but can actually make the code harder to read, plus are a PIA in many ways due to operator overloading and all that. Plus there is the minor manner of it being nice to have tools that can check legacy code.

    I'd be (pleasantly) surprised to see how superlexx did it with /analyze - I used /analyze on the code in VS 2010 Ultimate and it didn't flag this. Do I need some #define for the SAL annotations to not be preprocessed out or something?

  • Well I took a look at actual timings for cout and printf.  cout is indeed slower (but it shouldn't be), so I stepped through it to see why and I noticed something pretty terrible.  When writing a string cout calls _Fputc, which acquires and releases a lock on the stream every time a single character is output.  Printf does not do this, instead using write_char which does no locking.  No wonder people don't use c++ on windows... Microsoft purposefully sabotages performance!

  • I added a performance bug on the relative speeds of cout and printf:

    connect.microsoft.com/.../std-wcout-is-ten-times-slower-than-wprintf-performance-bug-in-c-library

    The code below outputs a string 4000 times using wprintf and wcout. It shoudl take roughly the same amount of time. But it actually takes more than ten times as long with wcout. Please find out why and fix wcout.

    void usePrintf()

    {

       for( unsigned i=0; i < 4000; i++ ) {

           wprintf( L"%s", L"cupcakes are made of flour, eggs, and sugar" );

       }

    }

    void useCout()

    {

       for( unsigned i=0; i < 4000; i++ ) {

           std::wcout << L"cupcakes are made of flour, eggs, and sugar";

       }

    }

    int _tmain(int argc, _TCHAR* argv[])

    {

       long    tick0= ::GetTickCount();

       usePrintf();

       long    tick1= ::GetTickCount();

       useCout();

       long    tick2= ::GetTickCount();

       //     DEBUG                                            RELEASE

       // printf took     671 milliseconds.    printf took     593 milliseconds.

       // wcout took 11653 milliseconds.    wcout took 10779 milliseconds.

       std::wcout << L"printf took " << tick1 - tick0 << L" milliseconds." << std::endl

                        << L"wcout took " << tick2 - tick1 << L" milliseconds." << std::endl;

       return 0;

    }

  • Well I take back my "MS did it on purpose" bit.  I suppose it's possible that the standard wants character level synchronization for certain streams, but I don't think anyone wants it on their output buffer!  cout should definitely not have this level of synchronization because of the performance issues it introduces.

  • I think it is actually a calling convention issue, not a packing/alignment what ever issue. The  64bit ABI passes structures by value by allocating the structure and just passing a pointer. For small strings std::string is storing the string, so in that situation on 64bit machines it will effectively be passing a pointer to a string. On 32bit on the other hand the struct is spilled directly to the stack and so it will dereference the contents.

    Of course things reverse if the string is > 15 characters long :)

  • While we're on the topic of iostreams performance, let me point out that other implementations are also an order of magnitude (or worse) slower than the C standard library.  I welcome all comers to the discussion here: stackoverflow.com/.../4340396

  • Thanks for the answers, critics, recommendations, etc, guys! The original purpose I intended to do with this puzzle was to check how much/little people could be influenced by the confusing fact that in some hardware architecture works -occasionally!!- fine while in another one fails. Confusing in the sense that it may be tempting to think that the issue is in the architecture and not in the code itself. How many times in the real-world we are before this kind of problems, where you don't know if the clues you have are just casual facts, relevant ones, part of the problem or the solution?

    Despite that, you guys seemed to have found the failure pretty quick: printf(...) when %s is declared in the formatting template expects somethings like char* rather than std::string. In fact, printf predates C++ and therefore C++ STL.

    The latter debates were not lesser interesting. STL performance over old-C one (with a guy opening a bug, APPLAUSE). While it's expected that C++ object-orientation brings flexibility thru abstraction at the cost of some performance, it may be revisable whether the current version isn't too much slower. Same for the better job that the tool could do warning that a suspicious data conversion may happen.

    And the typical, inevitable clash between C++ and C custodians :-)

    I have no preference, must say: I use both, usually C++ but switching to C when the total cost of my intent is to be lower.

  • The real problem is that VC really needs to add GCC-style printf argument type-check warnings.  I see this kind of crash all the time when programmers forget to add .c_str().

  • I wonder why you experience problems with the /analyze switch, it just works for me:

    c:\Users\User\AppData\Local\Temp\test>type test.cpp

    #include <tchar.h>

    #include <cstdio>

    int _tmain(int argc, _TCHAR* argv[])

    {

    struct foo {};

    printf("%s", foo());

    return 0;

    }

    c:\Users\User\AppData\Local\Temp\test>cl /analyze test.cpp

    Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86

    Copyright (C) Microsoft Corporation.  All rights reserved.

    test.cpp

    c:\users\user\appdata\local\temp\test\test.cpp(7) : warning C6284: Object passed as parameter '2' when string is required in call to 'printf'

Page 2 of 2 (24 items) 12