Ist `string.assign (string.data (), 5)` gut definiert oder UB?

11

Ein Mitarbeiter wollte dies schreiben:

std::string_view strip_whitespace(std::string_view sv);

std::string line = "hello  ";
line = strip_whitespace(line);

Ich sagte, dass die Rückkehr string_viewmich a priori unruhig machte , und außerdem sah das Aliasing hier für mich wie UB aus.

Ich kann mit Sicherheit sagen, dass line = strip_whitespace(line)in diesem Fall gleichbedeutend ist mit line = std::string_view(line.data(), 5). Ich glaube, das wird rufen string::operator=(const T&) [with T=string_view], was als äquivalent line.assign(const T&) [with T=string_view]definiert ist, was als äquivalent line.assign(line.data(), 5)definiert ist, was definiert ist, um dies zu tun:

Preconditions: [s, s + n) is a valid range.
Effects: Replaces the string controlled by *this with a copy of the range [s, s + n).
Returns: *this.

Dies sagt jedoch nicht aus, was passiert, wenn Aliasing stattfindet.

Ich habe diese Frage gestern auf dem cpplang Slack gestellt und gemischte Antworten bekommen. Suchen Sie hier nach super maßgeblichen Antworten und / oder empirischen Analysen der Implementierungen realer Bibliotheksanbieter.


Ich schrieb Testfälle für string::assign, vector::assign, deque::assign, list::assign, und forward_list::assign.

  • Mit Libc ++ funktionieren alle diese Testfälle.
  • Mit Libstdc ++ funktionieren alle außer forward_listden Segfaults.
  • Ich weiß nichts über die Bibliothek von MSVC.

Der Segfault in libstdc ++ gibt mir Hoffnung, dass dies UB ist; Ich sehe aber auch, dass sowohl libc ++ als auch libstdc ++ große Anstrengungen unternehmen, damit dies zumindest in den üblichen Fällen funktioniert.

Quuxpluson
quelle
Haben Sie die Testfälle mit ASan kompiliert und / oder unter Valgrind ausgeführt? Dies würde das Rätselraten erleichtern, ob der Code Zugriffsverletzungen verursacht, obwohl dies in der Praxis möglicherweise nicht per Definition funktioniert.
Konrad Rudolph
1
"Wenn eine Mitgliedsfunktion oder ein Operator von basic_string eine Ausnahme auslöst, hat diese Funktion oder dieser Operator keine andere Auswirkung auf das basic_string-Objekt." - Dies erzwingt die Zuweisung von Speicher, bevor der vorhandene Speicher freigegeben wird, sodass eine Ausnahme ausgelöst wird, wenn die Zuweisung fehlschlägt, ohne Änderungen vorzunehmen *this. Ich sehe jedoch nichts, was die Wiederverwendung des vorhandenen Speichers verhindern könnte. In diesem Fall wird dies nicht angegeben, da die Semantik des Kopierens des Speichers nicht angegeben ist.
Sam Varshavchik
2
Bei den genannten Sequenzcontainern handelt es sich sicherlich um UB, da die assignAnforderungen in [tab: container.seq.req] vorbedingt verletzt wurden .
Walnuss

Antworten:

8

Abgesehen von einigen Ausnahmen, von denen Ihre keine ist, macht das Aufrufen einer Nicht-Konstanten-Mitgliedsfunktion (dh assign) für eine Zeichenfolge [...] Zeiger [...] auf ihre Elemente ungültig . Dies verstößt gegen die Voraussetzung auf , assigndass [s, s + n)ein gültiger Bereich, so dass diese nicht definiertes Verhalten ist.

Beachten Sie, dass string::operator=(string const&)die Sprache speziell dafür vorgesehen ist, die Selbstzuweisung zu einem No-Op zu machen.

ecatmur
quelle
1
Was genau ist der Punkt der Ungültigmachung und der Punkt, an dem die Voraussetzung erfüllt sein muss? Die Antwort scheint anzunehmen, dass die Vorbedingung nach dem Aufruf der Mitgliedsfunktion gelten muss.
Walnuss
1
@walnut Ich bin kein Sprachanwalt (auch keine Person mit besonders erweiterten C ++ - Kenntnissen), aber wenn wir Ihr Szenario umkehren, können wir eine Frage stellen - könnte der Bereich während der Ausführung von ungültig gemacht werdenassign ? Wenn ja, müssten wir einen bestimmten Punkt in der Implementierung von assign festlegen, um zu markieren, wann genau die Ungültigmachung auftreten kann, und ich glaube, dass C ++ dies nicht tun würde. Ich könnte mich jedoch irren.
Fureeish
2
@Fureeish Ich weiß es auch nicht, aber siehe zB LWG-Ausgabe 526 , geschlossen als " kein Defekt ", die in ihrer Empfehlung zum Schließen erwähnt, dass std::vector::insert(iterator pos, const T& value)funktionieren muss, wenn valuees sich um den Vektor selbst handelt, da der Standard dies nicht spezifiziert darf nicht funktionieren, obwohl diese Referenz durch den Aufruf möglicherweise ungültig wird.
Walnuss
1
@walnut " ist erforderlich, um zu arbeiten, weil der Standard keine Erlaubnis gibt, dass es nicht funktioniert. " - liebe es . Sooo ... lohnt es sich zu fragen, was in der Praxis passiert ? Ist die Implementierung erforderlich, um in einer solchen Situation eine Kopie des Arguments zu erstellen? Wie könnten Sie es realistisch umsetzen? Ich habe von Standard gehört, bei dem Compiler das Unmögliche tun müssen - ist dies einer dieser Fälle? Trotzdem danke für den Kommentar!
Fureeish
1
@Fureeish Eigentlich hat mein vorheriges (jetzt gelöschtes) Beispiel nicht wirklich getestet, was ich testen wollte. Hier ist ein festes Beispiel, das zeigt, dass sowohl libc ++ als auch libstdc ++ tatsächlich kopieren, bevor die Neuzuweisung nach Bedarf fortgesetzt wird.
Walnuss