Using sprintf with std::string in C++
Solution 1
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...
;
Solution 2
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 aconst char*
. You may not pass it tosprintf
. -
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 forstd::string
, then you're probably better off sprinting intostd::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.
Solution 3
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;
}
Solution 4
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
Solution 5
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();
Dan The Man
Updated on July 17, 2022Comments
-
Dan The Man almost 2 years
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.
-
Karsten Koop about 8 yearsAre you using
snprintf
? Becausesprintf
, 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 newstrSize
. -
M.M about 8 yearsThis code is very wrong.
reserve
does not change the size of the string, andsprintf
does not return negative just because you wrote out of bounds . You need to allocate the space you need before writing out of bounds. -
Thomas Perl almost 8 yearsRelated question: stackoverflow.com/questions/2342162/…
-
-
DevSolar about 8 yearsCompletely missing the point that you cannot write to the buffer returned by
c_str()
, and needlessly complicated even then since there issnprintf()
/snprintf_s
. -
Dan The Man about 8 yearshow can i simulate the string formatting in ostringstream, like magic=%04x and so on?
-
DevSolar about 8 years
-
M.M about 8 years
snprintf
is better than the null device hack. But you have to use that hack forswprintf
. -
DevSolar about 8 yearsInteresting; 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 ofstd::string
, but as for well-definedness, I have doubts. -
eerorika about 8 years@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. -
CiaPan about 8 yearsYour 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 atsprintf(output.c_str(), ...
expression. So it's false OP is usingsprintf
the way he described, because a presented excerpt is not a runnable code... -
Dan The Man about 8 years@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);
-
eerorika about 8 years@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 about 8 years@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.
-
CiaPan about 8 years@user2079303 Got it. Thank you for the explanation, I thought compilers are not allowed to accept such code.
-
DevSolar about 8 years@CiaPan: Quite generally speaking, a compiler's warnings and a language's rules are only losely coupled. This is doubly true for C/C++, which made it a credo that "the developer knows what he's doing".
-
Thomas Perl almost 8 yearsWouldn't you want to use
std::snprintf(&output[0], size, format, /* args go here */);
instead of the last line to be safer? Imagine that another thread modifies one of the format string args (say, an integer goes from 9 to 10, thereby taking up one more character) between theauto size = ...
assignment and thestd::sprintf()
call at the end. -
eerorika almost 8 years@ThomasPerl The size calculation, buffer allocation, and the output operation really only make sense in a non-data-race context. That's a good point. The size accepting version of
sprintf
would indeed avoid UB in a data race, but you would still have the possibility of arbitrarily cut-off output. You really should use a critical section to avoid a race here - and if you do, then the non-size and size using versions are equally safe. Or alternatively, simply use local copies of the non-const arguments so that they cannot be modified by other threads. -
jpa over 7 yearsMany code analysis tools will warn about sprintf(), so makes sense to always use snprintf().
-
hfrmobile about 7 years3rd party lib...?
-
Buster over 4 yearsIt's good. The string should be resized again at the end of the code snippet to snip off the terminating null.
-
LoPiTaL over 2 yearsWhy not
std::string output(size, '\0');
, without the +1? That constructor (4) copies N times the character AND adds a terminating\0
, effectively havingsize+1
bytes. If you pass insize+1
, it ends up havingsize+2
bytes, with the last "real" char set to\0
, which will cause problems when concatenating, size computing and so