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>intmain(){
usingnamespacestd::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::stringx("pq\0rs"); // Two characters because input assumed to be C-Stringstd::stringx("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.
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:
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:
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.
Na przykład umieściłem ten niewinnie wyglądający fragment w środku programu
// Create '\0' followed by '0' 40 times ;)std::stringstr("\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";
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.
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
usingnamespacestd::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3
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.
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::strings("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.
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ą.
Odpowiedzi:
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 aconst char*
zakłada, że dane wejściowe są łańcuchem C. C-stringi są\0
przerywane, a zatem analizowanie zatrzymuje się, gdy osiągnie\0
znak.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 ++ NIE
std::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 .\0
c_str()
Sprawdź również odpowiedź Douga T. na temat używania pliku
vector<char>
.Sprawdź również RiaD, aby uzyskać rozwiązanie C ++ 14.
źródło
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.
źródło
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;
Nie mam pojęcia, dlaczego chciałbyś to zrobić, ale spróbuj tego:
std::string my_string("a\0b", 3);
źródło
vector<unsigned char>
lubunsigned char *
zostały wymyślone.std::string
do 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.\0
Bajt w ciąg znaków UTF-8 może być tylko NUL. Znak zakodowany wielobajtowo nigdy nie będzie zawierał -\0
ani żadnego innego znaku ASCII.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"));
źródło
Poniższe będą działać ...
std::string s; s.push_back('a'); s.push_back('\0'); s.push_back('b');
źródło
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ęchar
i rozmiar.źródło
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
źródło
auto s{"a\0b"s};
Lepiej użyć std :: vector <char>, jeśli to pytanie nie jest tylko dla celów edukacyjnych.
źródło
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 jakS(/* 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::string
jak 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.
źródło
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")
źródło
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.
źródło