Zulässig, um den Nullterminator von std::string zu überschreiben?

Lesezeit: 6 Minuten

Zulassig um den Nullterminator von stdstring zu uberschreiben
Nicol Bola

In C++11 wissen wir das std::string ist garantiert sowohl zusammenhängend als auch nullterminiert (oder pedantischer terminiert durch charT(), was im Fall von char ist das Nullzeichen 0).

Es gibt diese C-API, die ich verwenden muss, die eine Zeichenfolge per Zeiger ausfüllt. Es schreibt die gesamte Zeichenfolge + Nullterminator. In C++03 war ich immer gezwungen, a zu verwenden vector<char>, denn davon konnte ich nicht ausgehen string zusammenhängend oder nullterminiert war. Aber in C++11 (unter der Annahme einer ordnungsgemäß konformen basic_string Klasse, die in manchen Standardbibliotheken noch zweifelhaft ist), kann ich.

Oder kann ich? Wenn ich das mache:

std::string str(length);

Die Zeichenfolge wird zugewiesen length+1 Bytes, wobei das letzte durch das Null-Terminator ausgefüllt wird. Das ist gut. Aber wenn ich das an die C-API weitergebe, wird es schreiben length+1 Zeichen. Es wird das Null-Terminator überschreiben.

Zugegeben, es wird das Null-Terminator überschreiben mit Nullzeichen. Die Chancen stehen gut, dass dies funktionieren wird (tatsächlich kann ich mir nicht vorstellen, wie es funktioniert konnte nicht arbeiten).

Aber was “funktioniert”, interessiert mich nicht. Ich möchte wissen, laut spez, ob es in Ordnung ist, den Nullterminator mit einem Nullzeichen zu überschreiben?

  • Richtig, aber die Frage von @NicolBolas lautet nicht “verursacht es ein Problem”, sondern “erlaubt die Spezifikation dies”.

    – nneonneo

    5. Oktober 12 um 6:22 Uhr


  • @texasbruce Mit anderen Worten, wen interessiert es, was die Spezifikation zulässt, wenn es auf Ihrem System funktioniert, verwenden Sie es? Zum Glück hat nicht jeder diese Einstellung.

    Benutzer743382

    5. Oktober 12 um 7:02 Uhr

  • Es sollte möglich sein, das Problem zu vermeiden, indem man a verwendet std::string mit einem zusätzlichen Nullzeichen am Ende, mit Länge length + 1, es sei denn, ich vermisse etwas?

    Benutzer743382

    5. Oktober 12 um 7:06 Uhr

  • @texasbruce Das ist völlig irrelevant. Der Punkt ist, dass nichts im Standard garantiert, dass sich die Null-Terminierung überhaupt an einer beschreibbaren Speicherstelle befindet. Es ist durchaus möglich (wenn auch unwahrscheinlich), dass es sich beispielsweise im Nur-Lese-Speicher befindet. Dann wird jeder Versuch, darauf zu schreiben, das Programm zum Absturz bringen. Jeder kompetente C-Programmierer wird Ihnen sagen, dass Sie völlig verrückt sind, wenn Sie versuchen, portable Programme zu schreiben, die diese Effekte ignorieren. es ist nicht überhaupt „ganz normal“.

    – Konrad Rudolf

    5. Oktober 12 um 8:31 Uhr


  • @FrerichRaabe Stimmt. Aber das ist eine ganz andere Diskussion. Und selbst dann lohnt es sich nicht, die Spezifikation zu ignorieren: Sie können sich immer noch bewusst dafür entscheiden brechen die Spezifikation – aber Sie sollten es zuerst wissen.

    – Konrad Rudolf

    5. Oktober 12 um 8:44 Uhr

Leider ist dies UB, wenn ich den Wortlaut richtig deute (ist jedenfalls nicht erlaubt):

§21.4.5 [string.access] p2

Kehrt zurück: *(begin() + pos) wenn pos < size(), andernfalls ein Verweis auf ein Objekt vom Typ T mit Wert charT(); der referenzierte Wert darf nicht verändert werden.

(Redaktionsfehler, dass es heißt T nicht charT.)

.data() und .c_str() weisen im Grunde zurück auf operator[] (§21.4.7.1 [string.accessors] p1):

Kehrt zurück: Ein Zeiger p so dass p + i == &operator[](i) für jede i in [0,size()].

  • Zählt das Schreiben von ‘’ in etwas, das bereits ‘’ ist, tatsächlich als Änderung?

    – Michael Anderson

    5. Oktober 12 um 6:57 Uhr


  • @MichaelAnderson, ja, definitiv. Es schreibt ins Gedächtnis.

    – Jonathan Wakely

    5. Oktober 12 um 7:43 Uhr

  • @KonradRudolph Tja, data und c_str geben const-Zeiger zurück, daher kommen sie sowieso nicht zum Modifizieren in Frage. Und ab 21.4.5 p2 folgt das nicht mehr wirklich *(&str[0] + str.size()) ist sogar erlaubt, da [] ist nur gleich *(begin()+pos) Pro pos < size(). Ich denke, eine Implementierung darf die Zeichenfolgendaten in a enthalten length Array zusammen mit einem zusätzlichen static const charT Member für die Null (natürlich bedeutet dies, dass ein zusätzlicher Puffer für die Rückgabe beibehalten werden müsste data und c_str, aber warum nicht?).

    – Christian Rau

    5. Oktober 12 um 8:44 Uhr


  • @KonradRudolph: Es muss. Der Puffer ist zusammenhängend. Deswegen, &str[str.size() - 1] == &str[str.size()] - 1 muss wahr sein (vorausgesetzt length() ist mindestens 1). Wenn dies nicht der Fall wäre, wäre der Puffer nicht zusammenhängend.

    – Nicol Bolas

    5. Oktober 12 um 8:58 Uhr


  • @NicolBolas Nr. size() ist ein ungültiges Argument für operator[] nichts garantiert also, dass sein Rückgabewert auf den Puffer zeigt. Zum Beispiel (weit hergeholt), operator[] könnte folgende Logik enthalten: static CharT terminator{}; if (index == size()) return terminator; else return _data[i];.

    – Konrad Rudolf

    5. Oktober 12 um 9:02 Uhr


Gemäß der Spezifikation wird die Terminierung überschrieben NUL sollte sein undefiniertes Verhalten. Richtig wäre also die Zuordnung length+1 Zeichen in der Zeichenfolge, übergeben Sie den Zeichenfolgenpuffer an die C-API und dann resize() zurück zu length:

// "+ 1" to make room for the terminating NUL for the C API
std::string str(length + 1);

// Call the C API passing &str[0] to safely write to the string buffer
...

// Resize back to length
str.resize(length);

(FWIW, ich habe den Ansatz “Überschreiben von NUL” auf MSVC10 ausprobiert, und er funktioniert einwandfrei.)

  • Ich würde auch zu dieser Lösung gehen. Aber es ist unbefriedigend, dass dies eine völlig unnötige Zuordnung eines zusätzlichen Zeichens erfordert. Warum hat die Spezifikation die Nullterminierung nicht einfach beschreibbar gemacht?

    – Konrad Rudolf

    5. Oktober 12 um 9:12 Uhr

  • Weil das mehr Tippen bedeuten würde? ‘Sie dürfen die Null nicht überschreiben, außer mit einer anderen Null’. Vielleicht wurden ihre Finger müde, oder es war Feierabend und die Bars hatten geöffnet.

    – Martin Jakob

    5. Oktober 12 um 9:21 Uhr

  • @KonradRudolph: Ich stimme zu, dass der Standard geändert werden sollte, sodass es möglich ist, a zu überschreiben NUL mit einem anderen NUL. Ich sehe keinen Grund, warum es nicht möglich sein sollte oder ein undefiniertes Verhalten auslösen sollte, und ich mag auch nicht die unnötige Zuweisung eines zusätzlichen Zeichens.

    – Herr C64

    5. Oktober 12 um 11:28 Uhr

LWG 2475 machte dies durch Bearbeiten der Spezifikation von gültig operator[](size()) (eingefügter Text in Fettschrift):

Gibt andernfalls einen Verweis auf ein Objekt des Typs zurück charT mit Wert
charT(), wo das Objekt geändert wird auf einen anderen Wert als charT()

führt zu undefiniertem Verhalten.

  • Ich sehe nicht, wie dies die Situation löst: Es bedeutet, dass Sie schreiben können s[s.size()] = ''; aber es ist immer noch nicht (in C ++ 14) definiert &s[s.size() - 1] + 1 ist dereferenzierbar (ganz zu schweigen davon, dass ein Null-Terminator entsteht); also im weiteren Sinne ein Algorithmus, der bei beginnt &s[0] und erhöht a char * kann das Null-Terminator immer noch nicht lesen oder schreiben. Die Sektion [string.require]/4 definiert diese Zeigerarithmetik nur für den Index, der streng kleiner als die Größe ist.

    – MM

    19. Juli 17 um 1:49 Uhr

  • @MM Das ist in der Spezifikation von versteckt data().

    – TC

    19. Juli 17 um 1:54 Uhr

  • Ich stimme zu, wenn data() als Quelle des Zeigers verwendet wird, dann ist alles gut, aber ich sehe nicht, wo &s[0] gibt die gleiche Garantie. Insbesondere sehe ich nichts, was die Implementierung daran hindert, den Nullterminator erst zu schreiben data() oder c_str() tatsächlich aufgerufen wird (und entweder geschrieben oder ein Dummy für die &s[s.size()] Fall).

    – MM

    19. Juli 17 um 2:00 Uhr

  • @MM Melden Sie dann ein LWG-Problem. Die Absicht hier ist ziemlich klar, wenn also der Wortlaut nicht übereinstimmt, fällt er in die Kategorie „offensichtlicher Mangel“.

    – TC

    19. Juli 17 um 2:08 Uhr


  • Ich weiß nicht, warum auf LWG 2475 in der Vergangenheitsform verwiesen wird, die Statusbericht zeigt, dass es noch nicht gelöst ist.

    – Ben Voigt

    19. Juli 17 um 2:18 Uhr

Ich nehme an, n3092 ist nicht mehr aktuell, aber das ist, was ich habe. Abschnitt 21.4.5 ermöglicht den Zugriff auf ein einzelnes Element. Es erfordert pos <= size(). Wenn pos < size() dann erhalten Sie das eigentliche Element, andernfalls (dh wenn pos == size()) dann erhalten Sie eine nicht änderbare Referenz.

Ich denke, dass in Bezug auf die Programmiersprache eine Art Zugriff, der den Wert ändern könnte, als Änderung angesehen wird, selbst wenn der neue Wert derselbe wie der alte Wert ist.

Hat g++ eine umständliche Bibliothek, auf die Sie verlinken können?

.

500020cookie-checkZulässig, um den Nullterminator von std::string zu überschreiben?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy