2015-03-13 10 views
6

Betrachten Sie den folgenden Code ein:Konvertieren von std :: string ** in char *** und es funktioniert. Wie?

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; 
} 

Klar ist das skizzenhafte Code, aber es passiert zu arbeiten.

http://ideone.com/2XAJYR

würde Ich mag wissen, warum es funktioniert? Gibt es einige seltsame Regeln von C++, von denen ich nichts weiß? Oder ist es nur schlechter Code und undefiniertes Verhalten?

Ich machte eine der Saiten sehr groß für den Fall, dass es eine kleine String-Optimierung gab.

Übernommen aus: Cast a vector of std::string to char***

+7

Es ist möglich, dass der String den Zeiger auf seinen Puffer als erstes Strukturelement speichert, daher ist seine Adresse dieselbe wie die des Stringobjekts. Ich würde nicht sagen "es funktioniert"; Es ist eher * vorgeben * zu arbeiten. –

+1

@TheParamagneticCroissant ja ich wette auch darauf, aber ich glaube nicht, dass es eine gute Idee ist, sich auf das Erstellen von Code zu verlassen, der immer funktionieren sollte ... –

+0

Ich denke, es hat mit std :: vector behaviour zu tun. Es ** garantiert ** kontinuierliches Gedächtnis und sogar kopiert und bewegt seine Daten herum, um diese Garantie zu erfüllen. Wenn sie statische std :: strings mit fester Speichergröße bekommen, werden sie in einen fortlaufenden Speicherblock gestellt und Sie können Tricks wie diesen machen :). Um es zu überprüfen, können Sie eine andere Zeichenfolge in Ihrem Vektor drücken und sehen, ob Ihre vorherigen Datenzeiger noch gültig sind. – Amadeusz

Antwort

7

Es ist sehr undefiniert Verhalten.

Es erscheint zu „arbeiten“, wenn der String-Implementierung einen Zeiger auf den Zeichenfolgendaten als einziges Datenelement enthalten, der Fall ist, so dass ein Array von string die gleiche Speicherlayout als ein Array von char* aufweist. Dies ist der Fall für mindestens eine gängige Implementierung (GNU), aber Sie können sich sicher nicht darauf verlassen.

+1

Dies ist besonders unangenehm, wenn Sie mit C-Bibliotheken arbeiten. Ich habe einige Fälle gesehen, in denen eine 'std :: string' als' void * 'übergeben wurde, was gut funktioniert, bis jemand sich entscheidet, mit' clang' zu rekompilieren. – Shep

+0

@Shep: Wenn Sie mit C-Bibliotheken arbeiten, verwenden Sie 'c_str()', um einen C-kompatiblen Zeiger in einer wohldefinierten Weise zu erhalten. Dodgy Typ-Punning ist in jeder Situation unangenehm. –

+0

@Shep außer, das könnte in Ordnung sein! Es gibt ein Paradigma, das als "undurchsichtige Zeiger" oder "undurchsichtige Griffe" bekannt ist, mit denen Sie z. Benutzerdaten, die an Callback-Funktionen übergeben werden sollen. Die Bibliothek interpretiert das "void *" in diesen Situationen in keiner Weise. Der Benutzer-Rückruf _findet heraus, was der Typ tatsächlich ist und kann ihn zurückwerfen. (PS. Natürlich ist das '& string' anstatt der String selbst als' void * ') – sehe

2

Das Verhalten auf dem STL-Implementierung abhängt (nur revidieren std :: vector und std :: string Quellcode). Gelegentlich haben Sie die Zeichenfolge impl, die (wie die anderen Teilnehmer bereits erwähnten) den Zeiger auf den Zeichenpuffer als Element speichert.

Es ist kein Geheimnis, dass man sich aufgrund von undefiniertem Verhalten nicht auf verkapselte Details der Implementierung verlassen sollte.

0

Wie der Kommentar von Parametric Croissant andeutet, ist es notwendig, dass das char[] Mitglied der String-Klasse das erste Mitglied ist, so dass die String-Adresse == char [] beginnt.

Ich konnte keine explizite Erwähnung dieser in der Norm finden. Es ist möglich, dass eine andere Regel im Standard diese implizit aufgibt, aber ich habe keine gefunden.

Daher sollten Sie sich nicht darauf verlassen.

Nota: Eine andere offensichtlichere Notwendigkeit ist, dass std :: vector zusammenhängende Speicherraum bietet, aber das ist angegeben.

2

Nach Neil Kirk erwähnt dies in einem Kommentar auf die Antwort, die ursprünglich all dies ausgelöst, ich habe es nachgeschlagen.

string ist eine Spezialisierung von basic_string auf alle Implementierungen.

Jetzt habe ich nur Zugriff auf Visual Studio 2013 Version von xstring.h (hier implementiert Microsoft basic_string), so dass dies für andere Versionen oder Compiler anders sein kann. Aber in xstring.h erbt basic_string von _String_alloc, die von _String_val erbt.

_String_val ist eigentlich die erste in der Vererbungskette, die Mitglied Variablen hat. Es ist erste Elementvariable, _Bx, ist eine union, die zu char* für string (nicht für wstring) übersetzt wird. So

wenn ein string zu einem char* auf Visual Studio 2013 gegossen wird, ist es ein char*, die auf die Membervariable zeigt beginnt: _Bx Da _Bxist eigentlich ein '\0'char* terminierte Sie können es cout und es ist richtig verhalten.

Nun, was ich nicht wusste, und was all diese Forschung hat mich gelehrt, dass _String_val auch eine Größe Variable enthält, _Mysize und einen reservierten Größe, _Myres. Wenn einer von ihnen in _String_val vor _Bx deklariert worden wäre, hätte dies zu Beginn der Ausgabe von cout jedes Stück Kauderwelsch ausgegeben.

Ich würde damit schließen, dass, wie in den anderen Antworten erwähnt, dieses Verhalten implementierungsabhängig ist und möglicherweise nicht über verschiedene Versionen oder Plattformen hinweg funktioniert.

+0

@BillLynch Danke, ich habe das aktualisiert. –

Verwandte Themen