Neil Kirk
Neil Kirk

Reputation: 21773

Converting std::string ** to char *** and it happens to work. How?

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***

Upvotes: 6

Views: 196

Answers (4)

Jonathan Mee
Jonathan Mee

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

Mike Seymour
Mike Seymour

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

F&#233;lix Cantournet
F&#233;lix Cantournet

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

Michael Grigoriev
Michael Grigoriev

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

Related Questions