Jak skonstruować std :: string z osadzonym null?

88

Jeśli chcę skonstruować std :: string z linią taką jak:

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

Tam, gdzie chcę mieć trzy znaki w wynikowym ciągu (a, null, b), otrzymuję tylko jeden. Jaka jest prawidłowa składnia?

Rachunek
źródło
4
Musisz z tym uważać. Jeśli zamienisz „b” na dowolny znak numeryczny, po cichu utworzysz niewłaściwy ciąg. Zobacz: stackoverflow.com/questions/10220401/…
David Stone,

Odpowiedzi:

129

Od C ++ 14

udało nam się stworzyć dosłowne 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";
}

Przed C ++ 14

Problem polega na tym, std::stringże konstruktor, który przyjmuje a const char*zakłada, że ​​dane wejściowe są łańcuchem C. C-stringi są \0przerywane, a zatem analizowanie zatrzymuje się, gdy osiągnie \0znak.

Aby to zrekompensować, musisz użyć konstruktora, który buduje ciąg z tablicy znaków (nie C-String). To wymaga dwóch parametrów - wskaźnika do tablicy i długości:

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.

Uwaga: C ++ NIEstd::string jest zakończony (jak sugerowano w innych postach). Można jednak wyodrębnić wskaźnik do wewnętrznego bufora, który zawiera C-String za pomocą metody . \0c_str()

Sprawdź również odpowiedź Douga T. na temat używania pliku vector<char>.

Sprawdź również RiaD, aby uzyskać rozwiązanie C ++ 14.

Martin York
źródło
7
aktualizacja: od C ++ 11 ciągi są zakończone znakiem null. Biorąc to pod uwagę, post Lokiego pozostaje ważny.
matthewaveryusa
14
@mna: Są zakończone wartością zerową w zakresie przechowywania, ale nie w tym sensie, że są zakończone wartością zerową ze znaczącym zakończeniem zerowym (tj. z semantyką definiującą długość łańcucha), co jest zwykłym znaczeniem tego terminu.
Wyścigi lekkości na orbicie
Dobrze wyjaśnione. Dziękuję Ci.
Joma,
22

Jeśli robisz manipulację tak, jak w przypadku łańcucha znaków w stylu c (tablicy znaków), rozważ użycie

std::vector<char>

Masz większą swobodę w traktowaniu go jak tablicy w taki sam sposób, w jaki traktowałbyś łańcuch c. Możesz użyć funkcji copy (), aby skopiować do ciągu:

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

i możesz go używać w wielu tych samych miejscach, w których możesz używać c-stringów

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

Naturalnie jednak cierpisz na te same problemy, co struny c. Możesz zapomnieć o swoim pustym terminalu lub pisać poza przydzielonym miejscem.

Doug T.
źródło
Jeśli mówisz, że próbujesz zakodować bajty do łańcucha (bajty grpc są przechowywane jako łańcuch), użyj metody wektorowej określonej w odpowiedzi; nie w zwykły sposób (patrz poniżej), który NIE skonstruuje całego ciągu 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

Nie mam pojęcia, dlaczego chciałbyś to zrobić, ale spróbuj tego:

std::string my_string("a\0b", 3);
17 z 26
źródło
1
Czego się obawiasz, żeby to zrobić? Czy kwestionujesz potrzebę przechowywania „a \ 0b” kiedykolwiek? lub kwestionujesz użycie std :: string do takiego przechowywania? Jeśli to drugie, co proponujesz jako alternatywę?
Anthony Cramp
3
@Constantin, to robisz coś nie tak, jeśli przechowujesz dane binarne jako ciąg. Po to vector<unsigned char>lub unsigned char *zostały wymyślone.
Mahmoud Al-Qudsi
2
Natknąłem się na to, próbując dowiedzieć się więcej o bezpieczeństwie strun. Chciałem przetestować mój kod, aby upewnić się, że nadal działa, nawet jeśli odczytuje znak null podczas odczytywania z pliku / sieci tego, co spodziewa się danych tekstowych. Używam std::stringdo wskazania, że ​​dane powinny być traktowane jako zwykły tekst, ale wykonuję trochę pracy haszującej i chcę się upewnić, że wszystko nadal działa z zaangażowanymi znakami zerowymi. Wydaje się, że jest to prawidłowe użycie literału ciągu z osadzonym znakiem null.
David Stone,
3
@DuckMaestro Nie, to nieprawda. \0Bajt w ciąg znaków UTF-8 może być tylko NUL. Znak zakodowany wielobajtowo nigdy nie będzie zawierał - \0ani żadnego innego znaku ASCII.
John Kugelman
1
Natknąłem się na to, próbując sprowokować algorytm w przypadku testowym. Więc są ważne powody; choć niewiele.
namezero
12

Jakie nowe możliwości dodają literały zdefiniowane przez użytkownika do C ++? przedstawia elegancką odpowiedź: Zdefiniuj

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

wtedy możesz stworzyć swój ciąg w ten sposób:

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

a nawet tak:

auto my_string = "a\0b"_s;

Jest sposób na „stary styl”:

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

wtedy możesz zdefiniować

std::string my_string(S("a\0b"));
anonim
źródło
8

Poniższe będą działać ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');
Andrew Stein
źródło
Musisz użyć nawiasów zamiast nawiasów kwadratowych.
jk.
5

Musisz z tym uważać. Jeśli zamienisz „b” na dowolny znak numeryczny, po cichu utworzysz niewłaściwy ciąg przy użyciu większości metod. Zobacz: Reguły dotyczące znaku zmiany znaczenia literałów ciągów C ++ .

Na przykład umieściłem ten niewinnie wyglądający fragment w środku programu

// 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";

Oto, co ten program wyświetla dla mnie:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

To była moja pierwsza instrukcja drukowania dwukrotnie, kilka niedrukowalnych znaków, po których następował znak nowej linii, a następnie coś w pamięci wewnętrznej, które właśnie nadpisałem (a następnie wydrukowałem, pokazując, że zostało nadpisane). Co najgorsze, nawet skompilowanie tego z dokładnymi i szczegółowymi ostrzeżeniami gcc nie dało mi żadnej wskazówki, że coś jest nie tak, a uruchomienie programu przez valgrind nie narzekało na żadne niewłaściwe wzorce dostępu do pamięci. Innymi słowy, jest to całkowicie niewykrywalne przez nowoczesne narzędzia.

Możesz uzyskać ten sam problem w znacznie prostszym std::string("0", 100);, ale powyższy przykład jest trochę trudniejszy, a zatem trudniej jest zobaczyć, co jest nie tak.

Na szczęście C ++ 11 daje nam dobre rozwiązanie problemu przy użyciu składni listy inicjalizacyjnej. Pozwala to uniknąć konieczności określania liczby znaków (co, jak pokazałem powyżej, można zrobić niepoprawnie) i pozwala uniknąć łączenia liczb uciekających. std::string str({'a', '\0', 'b'})jest bezpieczny dla dowolnej zawartości ciągu, w przeciwieństwie do wersji, które przyjmują tablicę chari rozmiar.

David Stone
źródło
2
W ramach przygotowań do tego posta przesłałem raport o błędzie do gcc w nadziei, że dodadzą ostrzeżenie, aby uczynić to trochę bezpieczniejszym: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone
4

W C ++ 14 możesz teraz używać literałów

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3
RiaD
źródło
1
a drugą linię można alternatywnie napisać, ładniej imho, jakauto s{"a\0b"s};
podkreślenie_d
Dobra odpowiedź Dzięki.
Joma,
1

Lepiej użyć std :: vector <char>, jeśli to pytanie nie jest tylko dla celów edukacyjnych.

Harold Ekstrom
źródło
1

odpowiedź anonym jest doskonała, ale w C ++ 98 jest też rozwiązanie inne niż makro:

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

Dzięki tej funkcji RawString(/* literal */)wygeneruje taki sam ciąg jak 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;

Dodatkowo występuje problem z makrem: wyrażenie nie jest w rzeczywistości takie, std::stringjak napisano, i dlatego nie może być używane np. Do prostej inicjalizacji przypisania:

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

... więc lepiej byłoby użyć:

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

Oczywiście w swoim projekcie powinieneś używać tylko jednego lub drugiego rozwiązania i nazywać je jak uważasz za stosowne.

Kyle Strand
źródło
-5

Wiem, że zadawano to pytanie od dawna. Ale dla każdego, kto ma podobny problem, może być zainteresowany poniższym kodem.

CComBSTR(20,"mystring1\0mystring2\0")
Dil09
źródło
Ta odpowiedź jest zbyt specyficzna dla platform Microsoft i nie odnosi się do pierwotnego pytania (które dotyczyło std :: string).
Czerwiec Rodos,
-8

Prawie wszystkie implementacje std :: strings są zakończone znakiem null, więc prawdopodobnie nie powinieneś tego robić. Zauważ, że „a \ 0b” ma w rzeczywistości cztery znaki ze względu na automatyczny terminator wartości null (a, null, b, null). Jeśli naprawdę chcesz to zrobić i złamać kontrakt std :: string, możesz zrobić:

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

ale jeśli to zrobisz, wszyscy twoi przyjaciele będą się z ciebie śmiać, nigdy nie znajdziesz prawdziwego szczęścia.

Jurney
źródło
1
std :: string NIE musi być zakończony wartością NULL.
Martin York
2
Nie jest to wymagane, ale w prawie wszystkich implementacjach jest tak, prawdopodobnie z powodu potrzeby, aby metoda dostępu c_str () zapewniła ci odpowiednik zakończony znakiem null.
Jurney
2
Ze względu na efektywność znak null może być przechowywany z tyłu bufora danych. Ale żadna z operacji (tj. Metod) na łańcuchu nie korzysta z tej wiedzy ani nie ma na nie wpływu łańcuch zawierający znak NULL. Znak NULL będzie manipulowany dokładnie w taki sam sposób, jak każdy inny znak.
Martin York
Dlatego to takie zabawne, że string jest std :: - jego zachowanie nie jest zdefiniowane na ŻADNEJ platformie.
Chciałbym, żeby użytkownik595447 wciąż tu był, abym mógł zapytać ich, o czym myślą, o czym mówią.
underscore_d