Consider the following code:
std::vector<std::string> foo{{"blee"}, {"bleck"}, {"blah0000000000000000000000000000000000000000000000000000000000000000000000000000000000"}};
std::string *temp = foo.data();
char*** bar = reinterpret_cast<char***>(&temp);
for (size_t i = 0; i < foo.size(); ++i){
std::cout << (*bar)[i] << std::endl;
}
Clearly this is sketchy code, but it happens to work.
http://ideone.com/2XAJYR
I would like to know why it works? Are there some strange rules of C++ I don't know about? Or is it just bad code and undefined behaviour?
I made one of the strings huge in case there was some small-string optimization going on.
Adapted from: Cast a vector of std::string to char***
You can't really "assign a string" to a char * , because although a char* parameter is sometimes referred to as a "string parameter", it isn't actually a string, it's a pointer (to a string).
Use std::string when you need to store a value. Use const char * when you want maximum flexibility, as almost everything can be easily converted to or from one.
We know that both string::c_str or string::data functions returns const char*. To get a non-const version, we can use the const_cast operator, which removes the const attribute from a class. This works in constant time as no copying is involved.
The difference between a string and a char* is that the char* is just a pointer to the sequence. This approach of manipulating strings is based on the C programming language and is the native way in which strings are encoded in C++.
It is very much undefined behaviour.
It will appear to "work" if the string implementation happens to contain a pointer to the string data as its only data member, so that an array of string
has the same memory layout as an array of char*
. That is the case for at least one popular implementation (GNU), but is certainly not something you can rely on.
The behaviour depends on your STL implementation (just revise std::vector and std::string source code). Occasionaly, you have the string impl that stores (as other participants mentioned) pointer to chars buffer as a member.
It's not a secret that one shoudn't rely on incapsulated details of implementation due to undefined behaviour it causes.
After Neil Kirk mentioned this in a comment on the answer that originally sparked all this, I looked it up.
string
is a specialization of basic_string
on all implementations.
Now I only have access to Visual Studio's 2013 version of xstring.h (here Microsoft implements basic_string
) so this may be different for other versions or compilers. But in xstring.h basic_string
inherits from _String_alloc
which inherits from _String_val
.
_String_val
is actually the first in the inheritance chain which has any member variables. It's first member variable, _Bx
, is a union
which will translate to a char*
for string
(not for wstring
).
So when a string
is cast to a char*
on Visual Studio 2013 it is a char*
which begins pointing to the member variable: _Bx
Since _Bx
is actually a '\0'
-terminated char*
you can cout
it and it behave's properly.
Now what I didn't know, and what all this research taught me, is that _String_val
also contains a size variable, _Mysize
, and a reserved size, _Myres
. If either of those had been declared in _String_val
before _Bx
this would have outputted gibberish at the start of cout
's output each line.
I'd conclude by conceding that as is mentioned by the other answers this behavior is implementation dependent, and may not work across diferent versions or platforms.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With