Ist string :: c_str () in C ++ 11 nicht mehr null terminiert?

72

In C ++ 11 basic_string::c_strist genau das gleiche definiert wie basic_string::data, was wiederum genau das gleiche ist wie *(begin() + n)und *(&*begin() + n)(wann 0 <= n < size()).

Ich kann nichts finden, bei dem die Zeichenfolge immer ein Nullzeichen am Ende haben muss.

Bedeutet dies, dass c_str()nicht mehr garantiert wird, dass eine nullterminierte Zeichenfolge erzeugt wird?

Mankarse
quelle
24
sicherlich würde solch eine drastische Änderung viele alte Codes brechen ...
Nim
3
@Nim: Ich stimme vollkommen zu, aber ich habe mich gefragt, wo in der Norm diese Anforderung angegeben ist.
Mankarse
6
Wenn c_strkeine NULL-terminierte Zeichenfolge zurückgegeben würde, wäre dies die am häufigsten falsch benannte Funktion aller Zeiten.
Seth Carnegie
1
Sie haben ein =In verpasst 0 <= n <= size()... alles ist in Ordnung, wenn Sie es einschließen, wie es der Standard tut
Ben Voigt

Antworten:

80

Zeichenfolgen sind jetzt erforderlich, um intern nullterminierte Puffer zu verwenden. Schauen Sie sich die Definition von operator[](21.4.5) an:

Benötigt : pos <= size() .

Rückgabe: *(begin() + pos) if pos < size(), andernfalls ein Verweis auf ein Objekt vom Typ Tmit Wert charT(); Der angegebene Wert darf nicht geändert werden.

Rückblickend auf c_str(21.4.7.1/1) sehen wir, dass es definiert ist in Bezug auf operator[]:

Rückgabe: Ein Zeiger p, der p + i == &operator[](i)für jeden iin [0,size()].

Und beide c_strund datamüssen O (1) sein, sodass die Implementierung effektiv gezwungen ist, nullterminierte Puffer zu verwenden.

Wie David Rodríguez - dribeas in den Kommentaren ausführt , bedeutet die Rückgabewertanforderung außerdem, dass Sie &operator[](0)als Synonym für verwenden können c_str(), sodass das abschließende Nullzeichen im selben Puffer liegen muss (da *(p + size())muss gleich sein charT()). Dies bedeutet auch, dass selbst wenn der Terminator träge initialisiert wird, es nicht möglich ist, den Puffer im Zwischenzustand zu beobachten.

Mikhail Glushenkov
quelle
6
Das sagt nichts darüber aus, dass die Zeichenfolge nullterminiert ist.
Jalf
21
Dies bedeutet zwar nicht, dass die Zeichenfolge nullterminiert sein muss, kann jedoch aus den Zeichenfolgenanforderungen abgeleitet werden. Beide c_strund datamüssen eine O (1) -Operation sein, was bedeutet, dass sie keine Kopie im laufenden Betrieb erstellen können. Darüber hinaus bedeutet das Erfordernis operator[], dass die Ausgabe übereinstimmt , dass sie entweder bereits nicht beendet ist oder dass der Aufruf von data/ c_strmuss den Abschluss nicht hinzufügen, bevor der Zeiger zurückgegeben wird. Darüber hinaus muss die Zeichenfolge vor dem Aufruf Platz für diesen Terminator haben , um die O (1) -Anforderung aufrechtzuerhalten. Technisch gesehen ist der String muss nicht nul beendet, sondern data()tut
David Rodríguez - dribeas
7
Auch das letzte Zitat: Rückgabe: Ein Zeiger p, so dass p + i == & operator [] (i) für jedes i in [0, size ()]. &operator[](size()) == &operator[](size()-1) + 1Bedeutet, dass - dh wenn operator[](size())ein Verweis auf eine \0außerhalb der Zeichenfolge zurückgegeben wird, diese Anforderung niemals erfüllt werden kann.
David Rodríguez - Dribeas
9
@jalf: Das sagt nichts darüber aus, dass die Zeichenfolge nullterminiert ist. Ja tut es. 21.4.7.1 besagt, dass der von zurückgegebene Zeiger c_str()auf einen Puffer der Länge zeigen muss size()+1. 21.4.5 besagt, dass das letzte Element dieses Puffers den Wert charT()- mit anderen Worten das Nullzeichen - haben muss.
David Hammen
4
@jalf: " Diese Antwort gibt uns nur die Hälfte der Inferenzkette. " Sie gibt zwei Drittel der gesamten Kette. Das einzige, was fehlt, ist, dass der durch die Standardinitialisierung zugewiesene Wert charT()das Nullzeichen ist. Dies ist eindeutig der Fall, wenn dies der Fall charTist char. Der Standard ist ein bisschen vage (mehr als ein bisschen vage) in Bezug auf die Bedeutung von wchar_t.
David Hammen
23

Tatsächlich stimmt der neue Standard, dass .data () und .c_str () jetzt Synonyme sind. Es heißt jedoch nicht, dass .c_str () nicht mehr nullterminiert ist :)

Es bedeutet nur, dass Sie sich jetzt darauf verlassen können, dass .data () auch mit Null abgeschlossen wird.

In Artikel N2668 werden die Mitglieder c_str () und data () von std :: basic_string wie folgt definiert:

 const charT* c_str() const; 
 const charT* data() const; 

Rückgabe: Ein Zeiger auf das Anfangselement eines Arrays der Länge size () + 1, dessen erste size () -Elemente den entsprechenden Elementen der von * this gesteuerten Zeichenfolge entsprechen und dessen letztes Element ein durch charT () angegebenes Nullzeichen ist.

Erforderlich: Das Programm darf keinen der im Zeichenarray gespeicherten Werte ändern.

Beachten Sie, dass dies NICHT bedeutet, dass ein gültiger std :: string als C-String behandelt werden kann, da std :: string eingebettete Nullen enthalten kann, die den C-String vorzeitig beenden, wenn sie direkt als const char * verwendet werden.

Nachtrag:

Ich habe keinen Zugriff auf die tatsächlich veröffentlichte endgültige Spezifikation von C ++ 11, aber es scheint, dass der Wortlaut tatsächlich irgendwo im Revisionsverlauf der Spezifikation gestrichen wurde: z. B. http://www.open-std.org/jtc1/ sc22 / wg21 / docs / papers / 2011 / n3242.pdf

§ 21.4.7 String-Operationen basic_string [string.ops]

§ 21.4.7.1 basic_string-Accessoren [string.accessors]

     const charT* c_str() const noexcept;
     const charT* data() const noexcept;
  1. Rückgabe: Ein Zeiger p, so dass p + i == &operator[](i)für jeden iin [0,size()].
  2. Komplexität: konstante Zeit.
  3. Erforderlich: Das Programm darf keinen der im Zeichenarray gespeicherten Werte ändern.
sehe sehen
quelle
@ R.MartinhoFernandes: Meine Bearbeitung und dein Kommentar müssen Beiträge gekreuzt haben?
sehe
1
Ja, tut mir leid. In Bezug auf Ihre Bearbeitung möchte ich darauf hinweisen, dass der FDIS-Wortlaut sich stark davon unterscheidet und die Anforderung der Nullterminierung nicht so offensichtlich ist, aber in :)
R. Martinho Fernandes
weitere Überarbeitungen ausgegraben. Nun, wer kauft mir diese Kopie der Spezifikation;)
sehe
Bitte entfernen Sie die eckigen Klammern, die als Teil Operator[](i)Ihres Beitrags angezeigt werden, da sie derzeit als Link interpretiert werden, wodurch der Text nicht mehr verständlich ist.
Kevin Cathcart
@ Kevin: sry darüber, behoben
sehe
10

Die "Geschichte" war, dass vor langer Zeit, als alle in einzelnen Threads arbeiteten oder zumindest die Threads Arbeiter mit ihren eigenen Daten waren, sie eine String-Klasse für C ++ entwarfen, die die Handhabung von Strings einfacher machte als zuvor, und sie überlasteten Operator + zum Verketten von Zeichenfolgen.

Das Problem war, dass Benutzer so etwas tun würden:

s = s1 + s2 + s3 + s4;

und jede Verkettung würde eine temporäre erzeugen, die eine Zeichenfolge implementieren musste.

Daher hatte jemand die Gehirnwelle der "faulen Auswertung", so dass Sie intern eine Art "Seil" mit allen Zeichenfolgen speichern konnten, bis jemand es als C-Zeichenfolge lesen wollte. An diesem Punkt würden Sie die interne Darstellung in einen zusammenhängenden Puffer ändern .

Dies löste das obige Problem, verursachte jedoch eine Menge anderer Kopfschmerzen, insbesondere in der Multithread-Welt, in der erwartet wurde, dass eine .c_str () -Operation schreibgeschützt ist / nichts ändert und daher nichts gesperrt werden muss. Vorzeitiges internes Sperren in der Klassenimplementierung für den Fall, dass jemand Multithreading durchführte (wenn es nicht einmal einen Threading-Standard gab), war ebenfalls keine gute Idee. Tatsächlich war es teurer, dies zu tun, als jedes Mal einfach den Puffer zu kopieren. Der gleiche Grund, warum die Implementierung "Beim Schreiben kopieren" für Zeichenfolgenimplementierungen abgebrochen wurde.

Daher .c_str()stellte sich heraus, dass es am sinnvollsten ist , eine wirklich unveränderliche Operation durchzuführen. Könnte man sich jedoch in einem Standard, der jetzt Thread-fähig ist, darauf "verlassen"? Aus diesem Grund hat der neue Standard entschieden, dass dies eindeutig möglich ist, und daher muss die interne Darstellung den Nullterminator enthalten.

Goldesel
quelle
Die alte stringhatte auch die seltsame Eigenschaft, dass die erste Nicht-Konstante begin()Iteratoren ungültig machen würde!
Neugieriger
2

Gut erkannt. Dies ist sicherlich ein Fehler in der kürzlich verabschiedeten Norm; Ich bin sicher, dass es nicht die Absicht gab, den gesamten aktuell verwendeten Code zu brechen c_str. Ich würde einen Fehlerbericht vorschlagen oder zumindest die Frage stellen comp.std.c++(die normalerweise vor dem Ausschuss landet, wenn es sich um einen Fehler handelt).

James Kanze
quelle
Nun, es gibt Teile im FDIS, die wohl wackelig sind. 21.4.2/2sagt, dass .data()für eine leere Zeichenfolge nicht wirklich nullterminiert ist ( .data()+1ist nicht gültig, sollte aber ein Zeiger jenseits der sein \0)
MSalters