Bagaimana Anda membuat std :: string dengan null tertanam?

89

Jika saya ingin membuat std :: string dengan garis seperti:

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

Di mana saya ingin memiliki tiga karakter dalam string yang dihasilkan (a, null, b), saya hanya mendapatkan satu. Apa sintaks yang tepat?

Tagihan
sumber
4
Anda harus berhati-hati dengan ini. Jika Anda mengganti 'b' dengan karakter numerik apa pun, Anda akan membuat string yang salah secara diam-diam. Lihat: stackoverflow.com/questions/10220401/…
David Stone

Jawaban:

129

Sejak C ++ 14

kami telah mampu menciptakan literal 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";
}

Sebelum C ++ 14

Masalahnya adalah std::stringkonstruktor yang const char*menganggap input adalah string-C. String C \0diakhiri dan penguraian berhenti saat mencapai \0karakter.

Untuk mengimbangi ini, Anda perlu menggunakan konstruktor yang membangun string dari array karakter (bukan C-String). Ini membutuhkan dua parameter - pointer ke array dan panjang:

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.

Catatan: C ++ std::stringadalah TIDAK \0 -terminated (seperti yang disarankan dalam posting lain). Namun, Anda dapat mengekstrak pointer ke buffer internal yang berisi C-String dengan metode tersebut c_str().

Lihat juga jawaban Doug T di bawah ini tentang penggunaan a vector<char>.

Lihat juga RiaD untuk solusi C ++ 14.

Martin York
sumber
8
update: pada c ++ 11 string dihentikan null. Meski begitu, postingan Loki tetap valid.
matthewaveryusa
14
@mna: Mereka diakhiri null dalam hal penyimpanan, tetapi tidak dalam arti bahwa mereka diakhiri null dengan penghentian null yang berarti (yaitu dengan semantik yang menentukan panjang string), yang merupakan arti umum dari istilah tersebut.
Balapan Ringan di Orbit
Dijelaskan dengan baik. Terima kasih.
Joma
22

Jika Anda melakukan manipulasi seperti yang Anda lakukan dengan string gaya-c (larik karakter) pertimbangkan untuk menggunakan

std::vector<char>

Anda memiliki lebih banyak kebebasan untuk memperlakukannya seperti array dengan cara yang sama seperti Anda memperlakukan c-string. Anda dapat menggunakan copy () untuk menyalin ke dalam string:

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

dan Anda bisa menggunakannya di banyak tempat yang sama Anda bisa menggunakan c-string

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

Secara alami, bagaimanapun, Anda mengalami masalah yang sama seperti c-string. Anda mungkin lupa terminal null Anda atau menulis melewati ruang yang dialokasikan.

Doug T.
sumber
Jika Anda mengatakan mencoba menyandikan byte ke string (byte grpc disimpan sebagai string) gunakan metode vektor seperti yang ditentukan dalam jawaban; bukan cara yang biasa (lihat di bawah) yang TIDAK akan membangun seluruh string 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

Saya tidak tahu mengapa Anda ingin melakukan hal seperti itu, tetapi coba ini:

std::string my_string("a\0b", 3);
17 dari 26
sumber
1
Apa kekhawatiran Anda untuk melakukan ini? Apakah Anda pernah mempertanyakan perlunya menyimpan "a \ 0b"? atau mempertanyakan penggunaan std :: string untuk penyimpanan tersebut? Jika yang terakhir, apa yang Anda sarankan sebagai alternatif?
Anthony Cramp
3
@ Constantin maka Anda melakukan kesalahan jika Anda menyimpan data biner sebagai string. Untuk itulah vector<unsigned char>atau unsigned char *diciptakan.
Mahmoud Al-Qudsi
2
Saya menemukan ini saat mencoba mempelajari lebih lanjut tentang keamanan string. Saya ingin menguji kode saya untuk memastikan bahwa kode itu masih berfungsi meskipun membaca karakter null saat membaca dari file / jaringan yang diharapkan menjadi data tekstual. Saya gunakan std::stringuntuk menunjukkan bahwa data harus dianggap sebagai teks biasa, tetapi saya melakukan beberapa pekerjaan hashing dan saya ingin memastikan semuanya masih berfungsi dengan karakter nol yang terlibat. Itu sepertinya penggunaan literal string yang valid dengan karakter null yang disematkan.
David Stone
3
@DuckMaestro Tidak, itu tidak benar. Sebuah \0byte dalam UTF-8 string hanya dapat NUL. Karakter yang dienkode multi-byte tidak akan pernah berisi \0--atau karakter ASCII lainnya dalam hal ini.
John Kugelman
1
Saya menemukan ini ketika mencoba memprovokasi algoritma dalam kasus uji. Jadi ada alasan yang sah; meskipun sedikit.
namezero
12

Kemampuan baru apa yang ditambahkan literal yang ditentukan pengguna ke C ++? menyajikan jawaban yang elegan: Tentukan

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

maka Anda dapat membuat string Anda dengan cara ini:

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

atau bahkan lebih:

auto my_string = "a\0b"_s;

Ada cara "gaya lama":

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

lalu Anda bisa mendefinisikan

std::string my_string(S("a\0b"));
orang dgn nama yg tdk dikenal
sumber
8

Berikut ini akan bekerja ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');
Andrew Stein
sumber
Anda harus menggunakan tanda kurung dari tanda kurung siku.
jk.
5

Anda harus berhati-hati dengan ini. Jika Anda mengganti 'b' dengan karakter numerik apa pun, Anda akan membuat string yang salah secara diam-diam menggunakan sebagian besar metode. Lihat: Aturan untuk karakter pelarian literal string C ++ .

Misalnya, saya menjatuhkan potongan yang tampak tidak bersalah ini di tengah-tengah program

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

Inilah hasil program ini untuk saya:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

Itu adalah pernyataan cetak pertama saya dua kali, beberapa karakter non-cetak, diikuti oleh baris baru, diikuti oleh sesuatu di memori internal, yang baru saja saya timpa (dan kemudian dicetak, menunjukkan bahwa itu telah ditimpa). Yang terburuk dari semuanya, bahkan mengkompilasi ini dengan peringatan gcc yang menyeluruh dan bertele-tele tidak memberi saya indikasi ada sesuatu yang salah, dan menjalankan program melalui valgrind tidak mengeluh tentang pola akses memori yang tidak tepat. Dengan kata lain, itu sama sekali tidak terdeteksi oleh alat modern.

Anda bisa mendapatkan masalah yang sama dengan yang lebih sederhana std::string("0", 100);, tetapi contoh di atas sedikit lebih rumit, dan karenanya lebih sulit untuk melihat apa yang salah.

Untungnya, C ++ 11 memberi kami solusi yang baik untuk masalah menggunakan sintaks daftar penginisialisasi. Ini menyelamatkan Anda dari keharusan untuk menentukan jumlah karakter (yang, seperti yang saya tunjukkan di atas, Anda dapat melakukannya dengan tidak benar), dan menghindari penggabungan nomor yang lolos. std::string str({'a', '\0', 'b'})aman untuk konten string apa pun, tidak seperti versi yang menggunakan larik chardan ukuran.

David Stone
sumber
2
Sebagai bagian dari persiapan saya untuk posting ini, saya mengirimkan laporan bug ke gcc dengan harapan mereka akan menambahkan peringatan agar ini sedikit lebih aman: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone
4

Di C ++ 14 Anda sekarang dapat menggunakan literal

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3
RiaD
sumber
1
dan baris ke-2 sebagai alternatif dapat ditulis, lebih baik imho, sepertiauto s{"a\0b"s};
underscore_d
Jawaban bagus Terima kasih.
Joma
1

Lebih baik menggunakan std :: vector <char> jika pertanyaan ini bukan hanya untuk tujuan pendidikan.

Harold Ekstrom
sumber
1

jawaban anonym sangat bagus, tetapi ada solusi non-makro di C ++ 98 juga:

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

Dengan fungsi ini, RawString(/* literal */)akan menghasilkan string yang sama seperti 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;

Selain itu, ada masalah dengan makro: ekspresi sebenarnya tidak std::stringseperti yang tertulis, dan oleh karena itu tidak dapat digunakan misalnya untuk inisialisasi-tugas sederhana:

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

... jadi mungkin lebih baik menggunakan:

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

Jelas Anda hanya boleh menggunakan satu atau solusi lain dalam proyek Anda dan menyebutnya apa pun yang menurut Anda sesuai.

Kyle Strand
sumber
-5

Saya tahu sudah lama pertanyaan ini ditanyakan. Tetapi bagi siapa saja yang mengalami masalah serupa mungkin tertarik dengan kode berikut.

CComBSTR(20,"mystring1\0mystring2\0")
Dil09
sumber
Jawaban ini terlalu spesifik untuk platform Microsoft dan tidak menjawab pertanyaan asli (yang menanyakan tentang std :: string).
Juni Rhodes
-8

Hampir semua implementasi std :: strings dihentikan oleh null, jadi Anda sebaiknya tidak melakukan ini. Perhatikan bahwa "a \ 0b" sebenarnya terdiri dari empat karakter karena terminator null otomatis (a, null, b, null). Jika Anda benar-benar ingin melakukan ini dan memutuskan kontrak std :: string, Anda dapat melakukan:

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

tetapi jika Anda melakukannya, semua teman Anda akan menertawakan Anda, Anda tidak akan pernah menemukan kebahagiaan sejati.

Jurney
sumber
1
std :: string TIDAK diperlukan untuk diakhiri NULL.
Martin York
2
Ini tidak diharuskan, tetapi di hampir semua implementasi, itu, mungkin karena kebutuhan untuk aksesor c_str () untuk memberi Anda setara yang diakhiri dengan null.
Jurney
2
Untuk efisiensi, karakter null dapat disimpan di belakang buffer data. Tetapi tidak ada operasi (yaitu metode) pada string yang menggunakan pengetahuan ini atau dipengaruhi oleh string yang berisi karakter NULL. Karakter NULL akan dimanipulasi dengan cara yang persis sama seperti karakter lainnya.
Martin York
Inilah mengapa sangat lucu bahwa string adalah std :: - perilakunya tidak ditentukan di platform APA PUN.
Saya berharap pengguna595447 masih di sini sehingga saya dapat menanyakan apa yang mereka pikirkan tentang apa yang mereka bicarakan.
underscore_d