Getting different output with printf and cout - C++
I have a string I am trying to print. when I 开发者_开发知识库used cout
, it outputs perfectly but using printf
leaves it mangled.
Here is the code:
int main ( int argc, char *argv[] )
{
// Check to make sure there is a single argument
if ( argc != 2 )
{
cout<<"usage: "<< argv[0] <<" <filename>\n";
return 1;
}
// Grab the filename and remove the extension
std::string filename(argv[1]);
int lastindex = filename.find_last_of(".");
std::string rawname = filename.substr(0, lastindex);
cout << "rawname:" << rawname << endl;
printf("rawname: %s", rawname);
}
The cout
gives me "rawname: file"
printf
gives me "rawname: " and then a bunch of squiggly charactersit's because rawname is defined as a std::string. You need to use
printf("rawname: %s", rawname.c_str());
The reason is that printf with the %s is expecting a null terminated C string in memory. Whereas a std::string stl string isn't exactly raw - it eventually null terminates in your situation, not sure if that's even a guarantee, since the length is internally managed by stl container class.
Edit:
As pointed out in a comment, internally it's guaranteed to be null terminated. So what you're seeing as 'squiggly lines' is an output of all the allocated but not utilized (or initialized) memory in that string up until the null terminator character.
What works
printf("%s", my_string.c_str());
What was wrong - synopsis
Short illustration (assumptions explained later):
std::string s {
// members in unknown order
size_type member: 13 00 00 00 HEAP
const char* member: pointer C to ................ "this and that"
};
You print characters here ^^^^^^ not here ^^^^^.
You can't pass non-POD data to functions - such as printf()
- that accept arbitrary numbers of arguments using ...
. ("..." parameters is a feature C++ inherits from C, and it's inherently unsuitable for use with complicated C++ objects).
You can even compile that?
My GCC compilers don't like this:
printf("rawname: %s", rawname);
GCC 4.5.2 error:
cannot pass objects of non-trivially-copyable
type 'struct std::string' through '...'
GCC 4.1.2 warning + runtime behaviour:
cannot pass objects of non-POD type 'struct std::string'
through '...'; call will abort at runtime
# ./printf_string
zsh: illegal hardware instruction ./printf_string
They won't compile it, because there is no standard way to pass objects using ...
. The compiler can't work out from simply ...
whether they're needed by value or by reference/pointer, so won't know what code to generate.
But your compiler bravely did something. Let's consider what the std::string object looks like for a moment, then return to how your compiler might have received and accessed it.
The gizzards of a std::string object
The internals of a std::string aren't specified, but typically contain any of:
- a member recording the current size OR a pointer past the end of the string (ala
end()
)- either allows simple calculation of the other, but the couple Standard Library implementations I've checked optimise for a pointer/
end()
member and calculatedsize()
- works better with idiomatic iterator loops
- either allows simple calculation of the other, but the couple Standard Library implementations I've checked optimise for a pointer/
- a pointer to a character buffer on the heap (in practice it's likely kept NUL terminated and
c_str()
returns it directly, but this pointer - available via thedata()
member function, is allowed by the Standard to address non-NUL terminated text, so theoretically it could have a NUL terminator appended only whenc_str()
is called, orc_str()
might copy the text elsewhere then append the NUL and return a pointer to that new buffer) - a "short string optimisation" data buffer so strings of only a few characters need not use the heap
and/or
- a pointer to some reference-counted object elsewhere (that has the members above + a reference counter, a mutex, ...?)
Example: a simple string implementation storing text
These could be in any order. So, the simplest possibility is something like:
std::string s = "this and that";
Now,
"this and that" is a string literal, let's say at address "A"; this data is copied into the
string
; thestring
does not remember where it got it froms
is the actualstd::string
object, let's say at address "B"; let's imagine it's the simplest possible:size_type size_;
(will hold the value 13, beingstrlen("this and that")
)const char* p_data_;
will point to some newly allocated heap memory - let's say at address "C" - into which "this and that\0" has been copied
Crucially, address "A", address "B" and address "C" are different!
How printf() sees the std::string
If we had a bad compiler that would attempt to pass our std::string
object to printf()
, then there are two things printf()
might receive instead of the const char*
that "%s"
tells it to expect:
1) a pointer to the std::string
object, i.e. address "B"
2) sizeof(std::string)
bytes of data copied from address "A" to some stack address "B" and/or registers where printf()
would expect it if it could handle these things ;-P
printf()
then starts printing the bytes from that address as if they're characters until it finds a 0/NUL byte:
for scenario 1 above, it prints the bytes in the object, for example:
say
size_type
is 4 bytes and at the start of the object; with size 13 it might be 13, 0, 0, 0 or 0, 0, 0, 13 depending on whether the machine uses the big-endian or little-endian storage convention... given it stops at the first NUL, it would print character 13 (which happens to be an ASCII carriage-return/CR value, returning the cursor to the start of line) then stop, or it might print absolutely nothing. In your own case your string content were different, so it would have printed some other garbage, but probably only a character or two before hitting a 0/NUL.say a
const char*
to the heap-allocated buffer at "C" happens to be at the start of the object, then the individual characters in that address would be printed: for 32-bit pointers that's probably 4 garbage characters (assuming none of them happen to be 0/NUL), for 64-bit it'll be 8, then it'll continue with the next field in thestd::string
(likely aend()
-tracking pointer, but if it's asize_type
field that's much more likely to have a 0/NUL).
printf()
might interpret the first four bytes of thestd::string
object's data as a pointer to further textual data... this is a different from 1): say thesize_type
member was first and the value was 13,printf()
could mis-interpret that as aconst char*
to address 13, then attempt to read characters from there. This is practically guaranteed to crash before printing anything (on modern OSes), so it's very unlikely that this behaviour actually happened, which leaves us with "1".
You need to print the internal char* of the std::string:
printf("rawname: %s", rawname.c_str());
Try this
cout << "rawname:" << rawname << endl;
printf("rawname: %s", rawname.c_str());
rawname is not a char array, but an instance of the std::string class. To get the actual char array, you should call the c_str() function
Have you tried rawname.c_str() in the printf?
精彩评论