18

I am using sprintf function in C++ 11, in the following way:

std::string toString()
{
    std::string output;
    uint32_t strSize=512;
    do
    {
        output.reserve(strSize);
        int ret = sprintf(output.c_str(), "Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u",
            INDEX_RECORD_TYPE_SERIALIZATION_HEADER,
            FORAMT_VERSION,
            contentType,
            contentFormatVersion,
            magic,
            segmentId);

        strSize *= 2;
    } while (ret < 0);

    return output;
}

Is there a better way to do this, than to check every time if the reserved space was enough? For future possibility of adding more things.

3
  • 1
    Are you using snprintf? Because sprintf, as shown in your code, has no way to determine the buffer size. snprintf would also return the required buffer size, so you could just use the returned value +1 as new strSize. Commented Apr 28, 2016 at 8:20
  • 1
    This code is very wrong. reserve does not change the size of the string, and sprintf does not return negative just because you wrote out of bounds . You need to allocate the space you need before writing out of bounds.
    – M.M
    Commented Apr 28, 2016 at 8:25
  • Related question: stackoverflow.com/questions/2342162/… Commented Sep 16, 2016 at 9:44

7 Answers 7

23

Your construct -- writing into the buffer received from c_str() -- is undefined behaviour, even if you checked the string's capacity beforehand. (The return value is a pointer to const char, and the function itself marked const, for a reason.)

Don't mix C and C++, especially not for writing into internal object representation. (That is breaking very basic OOP.) Use C++, for type safety and not running into conversion specifier / parameter mismatches, if for nothing else.

std::ostringstream s;
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
  << " Version=" << FORMAT_VERSION
  // ...and so on...
  ;
std::string output = s.str();

Alternative:

std::string output = "Type=" + std::to_string( INDEX_RECORD_TYPE_SERIALIZATION_HEADER )
                   + " Version=" + std::to_string( FORMAT_VERSION )
                   // ...and so on...
                   ;
6
  • 3
    how can i simulate the string formatting in ostringstream, like magic=%04x and so on? Commented Apr 28, 2016 at 8:48
  • 1
    @danieltheman: setw, setfill.
    – DevSolar
    Commented Apr 28, 2016 at 8:50
  • Your comment 'The return value is a pointer to const char, and the function itself marked const, for a reason' implies that the code presented should not even compile due to const qualifier mismatch at sprintf(output.c_str(), ... expression. So it's false OP is using sprintf the way he described, because a presented excerpt is not a runnable code...
    – CiaPan
    Commented Apr 28, 2016 at 9:57
  • 2
    @CiaPan it doesn't technically imply that the code should not even compile. If we assume the knowledge that implicit conversion from const to non-const is ill-formed, then it implies that a compiler is not required to compile the code, but the compiler can support the conversion as a non-standard language extension. The compiler is simply required to give the user a diagnostic message, if the program is ill-formed according to the standard. If it does compile, then the write through the const pointer is UB.
    – eerorika
    Commented Apr 28, 2016 at 10:48
  • @user2079303 Got it. Thank you for the explanation, I thought compilers are not allowed to accept such code.
    – CiaPan
    Commented Apr 28, 2016 at 11:17
18

The C++ patterns shown in other answers are nicer, but for completeness, here is a correct way with sprintf:

auto format = "your %x format %d string %s";
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/);
std::string output(size + 1, '\0');
std::sprintf(&output[0], format, /* Arguments go here*/);

Pay attention to

  • You must resize your string. reserve does not change the size of the buffer. In my example, I construct correctly sized string directly.
  • c_str() returns a const char*. You may not pass it to sprintf.
  • std::string buffer was not guaranteed to be contiguous prior to C++11 and this relies on that guarantee. If you need to support exotic pre-C++11 conforming platforms that use rope implementation for std::string, then you're probably better off sprinting into std::vector<char> first and then copying the vector to the string.
  • This only works if the arguments are not modified between the size calculation and formatting; use either local copies of variables or thread synchronisation primitives for multi-threaded code.
9
  • 1
    Interesting; I am not quite sure if the construct (writing to &output[0]) is adhering to the letter of the standard, though. This will probably work for next to all implementations of std::string, but as for well-definedness, I have doubts.
    – DevSolar
    Commented Apr 28, 2016 at 8:59
  • @DevSolar If I remember correctly, contiguousness of std::string buffer is guaranteed since c++11. Prior to that, it was not guaranteed. As far as I know, there are no other reasons for this to not work.
    – eerorika
    Commented Apr 28, 2016 at 9:04
  • @user2079303 string constructor that gets a number in it? i don`t think there is such thing in c++, can you please elaborate? i am talking about this: std::string output(size + 1); Commented Apr 28, 2016 at 10:17
  • @danieltheman sorry, I meant to use the constructor that takes a size, and a char. I forgot that the second argument has no default value. The code is now fixed.
    – eerorika
    Commented Apr 28, 2016 at 10:20
  • 1
    Why not std::string output(size, '\0');, without the +1? That constructor (4) copies N times the character AND adds a terminating \0, effectively having size+1 bytes. If you pass in size+1, it ends up having size+2 bytes, with the last "real" char set to \0, which will cause problems when concatenating, size computing and so
    – LoPiTaL
    Commented Nov 30, 2021 at 7:56
6

We can mix code from here https://stackoverflow.com/a/36909699/2667451 and here https://stackoverflow.com/a/7257307 and result will be like that:

template <typename ...Args>
std::string stringWithFormat(const std::string& format, Args && ...args)
{
    auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...);
    std::string output(size + 1, '\0');
    std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...);
    return output;
}
1
  • I don't think you should add 1 to the string size for the null terminator. std::string doesn't require it. It should be rather std::string output(size, '\0'); instead.
    – vahancho
    Commented Jan 18, 2023 at 14:09
2

A better way is to use the {fmt} library. Ex:

std::string message = fmt::sprintf("The answer is %d", 42);

It exposes also a nicer interface than iostreams and printf. Ex:

std::string message = fmt::format("The answer is {}", 42);`

See:
https://github.com/fmtlib/fmt
http://fmtlib.net/latest/api.html#printf-formatting-functions

2
1

Your code is wrong. reserve allocates memory for the string, but does not change its size. Writing into the buffer returned by c_str does not change its size either. So the string still believes its size is 0, and you've just written something into the unused space in the string's buffer. (Probably. Technically, the code has Undefined Behaviour, because writing into c_str is undefined, so anything could happen).

What you really want to do is forget sprintf and similar C-style functions, and use the C++ way of string formatting—string streams:

std::ostringstream ss;
ss << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
   << " Version=" << FORAMT_VERSION
   << /* ... the rest ... */;
return ss.str();
0

Yes, there is!

In C, the better way is to associate a file with the null device and make a dummy printf of the desired output to it, to learn how much space would it take if actually printed. Then allocate appropriate buffer and sprintf the same data to it.

In C++ you could associate the output stream with a null device, too, and test the number of charactes printed with std::ostream::tellp. However, using ostringstream is a way better solution – see the answers by DevSolar or Angew.

2
  • 1
    Completely missing the point that you cannot write to the buffer returned by c_str(), and needlessly complicated even then since there is snprintf() / snprintf_s.
    – DevSolar
    Commented Apr 28, 2016 at 8:38
  • snprintf is better than the null device hack. But you have to use that hack for swprintf.
    – M.M
    Commented Apr 28, 2016 at 8:57
0

You can use an implementation of sprintf() into a std::string I wrote that uses vsnprintf() under the hood.

It splits the format string into sections of plain text which are just copied to the destination std::string and sections of format fields (such as %5.2lf) which are first vsnprintf()ed into a buffer and then appended to the destination.

https://gitlab.com/eltomito/bodacious-sprintf

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.