Reputation: 21773
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.
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***
Upvotes: 6
Views: 196
Reputation: 38919
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.
Upvotes: 2
Reputation: 254461
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.
Upvotes: 7
Reputation: 1991
As The Parametric Croissant's comment suggest, it is necessary for this to work that the char[]
member of the string class is the first member so that the string address == the char[] beginning.
I couldn't find any explicit mention of this in the standard. It is possible that some other rule in the standard implicitly imposes this one, but I didn't find one.
Therefore you shouldn't rely on it.
Nota : Another more obvious necessity is that std::vector provides contiguous memory space, but this is specififed.
Upvotes: 0
Reputation: 31
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.
Upvotes: 2