Wie konstruiert man einen std :: string mit einer eingebetteten Null?

87

Wenn ich einen std :: string mit einer Zeile wie der folgenden erstellen möchte:

std::string my_string("a\0b");

Wo ich drei Zeichen in der resultierenden Zeichenfolge haben möchte (a, null, b), bekomme ich nur eines. Was ist die richtige Syntax?

Rechnung
quelle
4
Sie müssen damit vorsichtig sein. Wenn Sie 'b' durch ein numerisches Zeichen ersetzen, erstellen Sie stillschweigend die falsche Zeichenfolge. Siehe: stackoverflow.com/questions/10220401/…
David Stone

Antworten:

127

Seit C ++ 14

Wir konnten buchstäblich schaffen std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

Vor C ++ 14

Das Problem ist, dass der std::stringKonstruktor a const char*annimmt, dass die Eingabe eine C-Zeichenfolge ist. C-Strings werden \0beendet und das Parsen stoppt daher, wenn es das \0Zeichen erreicht.

Um dies zu kompensieren, müssen Sie den Konstruktor verwenden, der die Zeichenfolge aus einem char-Array (nicht einer C-Zeichenfolge) erstellt. Dies erfordert zwei Parameter - einen Zeiger auf das Array und eine Länge:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

Hinweis: C ++ std::stringwird NICHT beendet \0 (wie in anderen Beiträgen vorgeschlagen). Sie können jedoch mit der Methode einen Zeiger auf einen internen Puffer extrahieren, der einen C-String enthält c_str().

Lesen Sie auch die Antwort von Doug T über die Verwendung von a vector<char>.

Schauen Sie sich auch RiaD für eine C ++ 14-Lösung an.

Martin York
quelle
6
Update: Ab C ++ 11 sind Zeichenfolgen nullterminiert. Davon abgesehen bleibt Lokis Beitrag gültig.
Matthewaveryusa
14
@mna: Sie sind in Bezug auf die Speicherung nullterminiert, jedoch nicht in dem Sinne, dass sie mit einer aussagekräftigen Nullterminierung (dh mit einer die Zeichenfolge längendefinierenden Semantik) nullterminiert sind , was die übliche Bedeutung des Begriffs ist.
Leichtigkeitsrennen im Orbit
Gut erklärt. Danke dir.
Joma
22

Wenn Sie Manipulationen wie mit einer Zeichenfolge im C-Stil (Array von Zeichen) durchführen, sollten Sie die Verwendung in Betracht ziehen

std::vector<char>

Sie haben mehr Freiheit, es wie ein Array zu behandeln, genauso wie Sie einen C-String behandeln würden. Sie können copy () verwenden, um in eine Zeichenfolge zu kopieren:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

und Sie können es an vielen der gleichen Stellen verwenden, an denen Sie C-Strings verwenden können

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

Natürlich leiden Sie jedoch unter den gleichen Problemen wie C-Strings. Sie können Ihr Null-Terminal vergessen oder über den zugewiesenen Speicherplatz hinaus schreiben.

Doug T.
quelle
Wenn Sie versuchen, Bytes in Zeichenfolgen zu codieren (grpc-Bytes werden als Zeichenfolge gespeichert), verwenden Sie die in der Antwort angegebene Vektormethode. nicht die übliche Art (siehe unten), die NICHT die gesamte Zeichenfolge konstruiert byte *bytes = new byte[dataSize]; std::memcpy(bytes, image.data, dataSize * sizeof(byte)); std::string test(reinterpret_cast<char *>(bytes)); std::cout << "Encoded String length " << test.length() << std::endl;
Alex Punnen
13

Ich habe keine Ahnung, warum Sie so etwas tun möchten, aber versuchen Sie Folgendes:

std::string my_string("a\0b", 3);
17 von 26
quelle
1
Was sind Ihre Bedenken dafür? Stellen Sie die Notwendigkeit in Frage, "a \ 0b" jemals zu speichern? oder die Verwendung eines std :: string für einen solchen Speicher in Frage stellen? Wenn letzteres, was schlagen Sie als Alternative vor?
Anthony Cramp
3
@Constantin, dann machen Sie etwas falsch, wenn Sie Binärdaten als Zeichenfolge speichern. Dafür wurden vector<unsigned char>oder unsigned char *wurden erfunden.
Mahmoud Al-Qudsi
2
Ich bin darauf gestoßen, als ich versucht habe, mehr über die Sicherheit von Zeichenfolgen zu erfahren. Ich wollte meinen Code testen, um sicherzustellen, dass er auch dann noch funktioniert, wenn er ein Nullzeichen einliest, während er aus einer Datei / einem Netzwerk liest, was er als Textdaten erwartet. Ich gebe std::stringan, dass die Daten als Klartext betrachtet werden sollen, aber ich mache einige Hashing-Arbeiten und möchte sicherstellen, dass alles immer noch mit Nullzeichen funktioniert. Dies scheint eine gültige Verwendung eines Zeichenfolgenliteral mit einem eingebetteten Nullzeichen zu sein.
David Stone
3
@ DuckMaestro Nein, das stimmt nicht. Ein \0Byte in einer UTF-8-Zeichenfolge kann nur NUL sein. Ein Multi-Byte-codiertes Zeichen enthält niemals - \0oder ein anderes ASCII-Zeichen.
John Kugelman
1
Ich bin darauf gestoßen, als ich versucht habe, einen Algorithmus in einem Testfall zu provozieren. Es gibt also triftige Gründe; wenn auch nur wenige.
Namezero
12

Welche neuen Funktionen fügen benutzerdefinierte Literale C ++ hinzu? präsentiert eine elegante Antwort: Definieren

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

Dann können Sie Ihre Zeichenfolge folgendermaßen erstellen:

std::string my_string("a\0b"_s);

oder sogar so:

auto my_string = "a\0b"_s;

Es gibt einen "alten" Weg:

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

dann können Sie definieren

std::string my_string(S("a\0b"));
anonym
quelle
8

Folgendes wird funktionieren ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');
Andrew Stein
quelle
Sie müssen Klammern in eckigen Klammern verwenden.
jk.
5

Sie müssen damit vorsichtig sein. Wenn Sie 'b' durch ein numerisches Zeichen ersetzen, erstellen Sie mit den meisten Methoden stillschweigend die falsche Zeichenfolge. Siehe: Regeln für C ++ - Zeichenfolgenliterale als Escapezeichen .

Zum Beispiel habe ich dieses unschuldig aussehende Snippet mitten in einem Programm abgelegt

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

Folgendes hat dieses Programm für mich ausgegeben:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

Das war meine erste Druckanweisung zweimal, mehrere nicht druckbare Zeichen, gefolgt von einem Zeilenumbruch, gefolgt von etwas im internen Speicher, das ich gerade überschrieben (und dann gedruckt habe, um zu zeigen, dass es überschrieben wurde). Das Schlimmste war, dass selbst das Kompilieren mit gründlichen und ausführlichen gcc-Warnungen keinen Hinweis darauf gab, dass etwas nicht stimmte, und das Ausführen des Programms über valgrind beschwerte sich nicht über falsche Speicherzugriffsmuster. Mit anderen Worten, es ist mit modernen Werkzeugen völlig nicht nachweisbar.

Sie können das gleiche Problem mit dem viel einfacheren bekommen std::string("0", 100);, aber das obige Beispiel ist etwas kniffliger und daher schwieriger zu erkennen, was falsch ist.

Glücklicherweise bietet C ++ 11 eine gute Lösung für das Problem mithilfe der Initialisierungslistensyntax. Dies erspart Ihnen die Angabe der Anzahl der Zeichen (was, wie oben gezeigt, falsch sein kann) und vermeidet das Kombinieren von Escape-Zahlen. std::string str({'a', '\0', 'b'})ist sicher für jeden String-Inhalt, im Gegensatz zu Versionen, die ein Array von charund eine Größe annehmen .

David Stone
quelle
2
Als Teil meiner Vorbereitung auf diesen Beitrag habe ich gcc einen Fehlerbericht übermittelt, in der Hoffnung, dass eine Warnung hinzugefügt wird, um dies ein wenig sicherer zu machen: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone
4

In C ++ 14 können Sie jetzt Literale verwenden

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3
RiaD
quelle
1
und die 2. Zeile kann alternativ geschrieben werden, schöner imho, alsauto s{"a\0b"s};
underscore_d
Schöne Antwort Danke.
Joma
1

Verwenden Sie besser std :: vector <char>, wenn diese Frage nicht nur zu Bildungszwecken dient.

Harold Ekstrom
quelle
1

Die Antwort von anonym ist ausgezeichnet, aber es gibt auch eine Nicht-Makro-Lösung in C ++ 98:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

Mit dieser Funktion RawString(/* literal */)wird dieselbe Zeichenfolge erzeugt wie S(/* literal */):

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

Außerdem gibt es ein Problem mit dem Makro: Der Ausdruck ist nicht std::stringwie geschrieben und kann daher nicht verwendet werden, z. B. für eine einfache Zuweisungsinitialisierung:

std::string s = S("a\0b"); // ERROR!

... daher ist es möglicherweise vorzuziehen, Folgendes zu verwenden:

#define std::string(s, sizeof s - 1)

Natürlich sollten Sie in Ihrem Projekt nur die eine oder andere Lösung verwenden und sie so nennen, wie Sie es für angemessen halten.

Kyle Strand
quelle
-5

Ich weiß, es ist lange her, dass diese Frage gestellt wurde. Aber für alle, die ein ähnliches Problem haben, könnte der folgende Code von Interesse sein.

CComBSTR(20,"mystring1\0mystring2\0")
Dil09
quelle
Diese Antwort ist zu spezifisch für Microsoft-Plattformen und geht nicht auf die ursprüngliche Frage ein (die nach std :: string gestellt wurde).
Juni Rhodos
-8

Fast alle Implementierungen von std :: strings sind nullterminiert, daher sollten Sie dies wahrscheinlich nicht tun. Beachten Sie, dass "a \ 0b" aufgrund des automatischen Nullterminators (a, null, b, null) tatsächlich vier Zeichen lang ist. Wenn Sie dies wirklich tun und den Vertrag von std :: string brechen möchten, können Sie Folgendes tun:

std::string s("aab");
s.at(1) = '\0';

Aber wenn Sie dies tun, werden alle Ihre Freunde Sie auslachen, Sie werden niemals wahres Glück finden.

Jurney
quelle
1
std :: string muss NICHT NULL terminiert sein.
Martin York
2
Dies ist nicht erforderlich, aber in fast allen Implementierungen ist dies wahrscheinlich darauf zurückzuführen, dass der Accessor c_str () Ihnen das nullterminierte Äquivalent bereitstellen muss.
Jurney
2
Für effeciency ein Nullzeichen kann auf der Rückseite des Datenpuffers gehalten werden. Keine der Operationen (dh Methoden) für eine Zeichenfolge verwendet dieses Wissen oder wird von einer Zeichenfolge beeinflusst, die ein NULL-Zeichen enthält. Das NULL-Zeichen wird genauso wie jedes andere Zeichen bearbeitet.
Martin York
Aus diesem Grund ist es so lustig, dass der String std :: ist - sein Verhalten ist auf KEINER Plattform definiert.
Ich wünschte, user595447 wäre noch hier, damit ich sie fragen könnte, worüber in aller Welt sie zu reden glaubten.
underscore_d