Saya sedang memecahkan beberapa masalah pada codeforces. Biasanya saya pertama kali memeriksa apakah karakternya huruf Inggris atas atau bawah kemudian kurangi atau tambahkan 32
untuk mengubahnya menjadi huruf yang sesuai. Tetapi saya menemukan seseorang melakukan ^= 32
hal yang sama. Ini dia:
char foo = 'a';
foo ^= 32;
char bar = 'A';
bar ^= 32;
cout << foo << ' ' << bar << '\n'; // foo is A, and bar is a
Saya telah mencari penjelasan untuk ini dan tidak mengetahuinya. Jadi mengapa ini berhasil?
c++
bit-manipulation
ascii
Devon
sumber
sumber
@
menjadi `dengan menggunakan^ 32
.toupper
dantolower
untuk beralih kasus.A
untukZ
. Tidak apa-apa asalkan Anda hanya peduli dengan bahasa Inggris (dan jangan menggunakan ejaan "naif", kata-kata seperti "kafe", atau nama-nama dengan diakritik ...), tetapi dunia bukan hanya bahasa Inggris.Jawaban:
Mari kita lihat tabel kode ASCII dalam biner.
Dan 32 adalah
0100000
satu-satunya perbedaan antara huruf kecil dan huruf besar. Jadi, beralih sedikit itu mengubah huruf.sumber
{
lebih pendek dari[
, jadi ini adalah huruf "lebih rendah". Tidak? Ok, saya akan menunjukkan diri saya: Dfoobar[]
danfoobar{}
menjadi nama panggilan yang identik, karena nama panggilan tidak peka terhadap huruf besar-kecil , dan IRC memiliki asal-usulnya di Skandinavia :)Ini menggunakan fakta daripada nilai-nilai ASCII telah dipilih oleh orang-orang yang benar-benar pintar.
Ini membalik-6 bit terendah 1 dari
foo
(bendera huruf besar dari ASCII semacam), mengubah sebuah huruf ASCII untuk kasus yang lebih rendah dan sebaliknya .Contoh
Dan dengan properti XOR
'a' ^ 32 == 'A'
,.Memperhatikan
C ++ tidak diperlukan untuk menggunakan ASCII untuk mewakili karakter. Varian lain adalah EBCDIC . Trik ini hanya berfungsi pada platform ASCII. Solusi yang lebih portabel adalah menggunakan
std::tolower
danstd::toupper
, dengan bonus yang ditawarkan menjadi sadar-lokal (itu tidak secara otomatis menyelesaikan semua masalah Anda, lihat komentar):1) Sebagaimana 32 adalah
1 << 5
(2 pangkat 5), ia membalik bit ke-6 (dihitung dari 1).sumber
tolower
dalam bahasa Jerman tidak hanya membutuhkan kamus, tetapi juga harus mampu menguraikan artinya.Izinkan saya untuk mengatakan bahwa ini - walaupun tampaknya cerdas - peretasan yang benar-benar bodoh. Jika seseorang merekomendasikan ini kepada Anda pada tahun 2019, pukul dia. Pukul dia sekeras yang Anda bisa.
Anda dapat, tentu saja, melakukannya dalam perangkat lunak Anda sendiri yang Anda dan orang lain gunakan jika Anda tahu bahwa Anda tidak akan pernah menggunakan bahasa apa pun selain bahasa Inggris. Kalau tidak, jangan pergi.
Peretasan itu bisa dibilang "OK" sekitar 30-35 tahun yang lalu ketika komputer tidak benar-benar melakukan banyak hal selain bahasa Inggris di ASCII, dan mungkin satu atau dua bahasa utama Eropa. Tapi ... tidak lagi begitu.
Peretasan ini bekerja karena huruf AS-Latin bagian atas dan bawah persis
0x20
terpisah satu sama lain dan muncul dalam urutan yang sama, yang hanya memiliki sedikit perbedaan. Yang mana, pada kenyataannya, ini sedikit hack, matikan.Sekarang, orang-orang yang membuat halaman kode untuk Eropa Barat, dan kemudian konsorsium Unicode, cukup pintar untuk menjaga skema ini misalnya Umlaut Jerman dan Vokal beraksen Prancis. Tidak demikian untuk ß yang (sampai seseorang meyakinkan konsorsium Unicode pada 2017, dan sebuah majalah cetak Fake News yang besar menulis tentang hal itu, benar-benar meyakinkan Duden - tidak ada komentar tentang itu) bahkan tidak ada sebagai versal (berubah menjadi SS) . Sekarang memang ada sebagai versal, tetapi keduanya adalah
0x1DBF
posisi yang terpisah, bukan0x20
.Namun, implementornya tidak cukup perhatian untuk mempertahankan hal ini. Misalnya, jika Anda menerapkan retas dalam beberapa bahasa Eropa Timur atau sejenisnya (saya tidak akan tahu tentang Cyrillic), Anda akan mendapatkan kejutan yang tidak menyenangkan. Semua karakter "hatchet" adalah contoh dari itu, huruf kecil dan huruf besar adalah satu terpisah. Peretasan tidak bekerja dengan baik di sana.
Ada banyak lagi yang perlu dipertimbangkan, misalnya, beberapa karakter tidak hanya berubah dari huruf kecil ke huruf besar sama sekali (mereka diganti dengan urutan yang berbeda), atau mereka dapat mengubah bentuk (memerlukan titik kode yang berbeda).
Jangan pernah berpikir tentang apa yang akan dilakukan peretasan ini untuk hal-hal seperti Thailand atau Cina (itu hanya akan memberi Anda omong kosong).
Menyimpan beberapa ratus siklus CPU mungkin sangat bermanfaat 30 tahun yang lalu, tetapi saat ini, sebenarnya tidak ada alasan untuk mengkonversi string dengan benar. Ada fungsi perpustakaan untuk melakukan tugas non-sepele ini.
Waktu yang dibutuhkan untuk mengonversi beberapa lusin kilobyte teks dengan benar dapat diabaikan saat ini.
sumber
Ini bekerja karena, seperti yang terjadi, perbedaan antara 'a' dan A 'dalam pengkodean ASCII adalah 32, dan 32 juga nilai bit keenam. Membalik bit ke-6 dengan OR eksklusif sehingga mengubah antara atas dan bawah.
sumber
Kemungkinan besar implementasi set karakter Anda adalah ASCII. Jika kita melihat tabel:
Kita melihat bahwa ada perbedaan
32
antara nilai huruf kecil dan huruf besar. Oleh karena itu, jika kita lakukan^= 32
(yang sama dengan mengganti bit paling signifikan ke-6), itu berubah antara karakter huruf kecil dan huruf besar.Perhatikan bahwa ini bekerja dengan semua simbol, bukan hanya huruf. Ini beralih karakter dengan karakter masing-masing di mana bit ke-6 berbeda, menghasilkan sepasang karakter yang beralih bolak-balik antara. Untuk huruf, masing-masing karakter huruf besar / kecil membentuk pasangan seperti itu. A
NUL
akan berubah menjadiSpace
dan sebaliknya, dan@
beralih dengan backtick. Pada dasarnya setiap karakter di kolom pertama pada bagan ini beralih dengan karakter satu kolom di atas, dan hal yang sama berlaku untuk kolom ketiga dan keempat.Saya tidak akan menggunakan retasan ini, karena tidak ada jaminan bahwa ini akan bekerja pada sistem apa pun. Cukup gunakan toupper dan tolower saja, dan permintaan seperti isupper .
sumber
32 ^ 32
adalah 0, bukan 64[a-z]
dan[A-Z]
"huruf". Sisanya adalah kebetulan yang mengikuti aturan yang sama. Jika seseorang meminta Anda untuk "huruf besar]", apakah itu? masih akan menjadi "]" - "}" bukan "huruf besar" dari "]".%32
"alignment" dalam sistem pengkodean ASCII. Inilah sebabnya mengapa bit0x20
adalah satu-satunya perbedaan antara versi huruf besar / kecil dari huruf yang sama. Jika ini bukan masalahnya, Anda harus menambah atau mengurangi0x20
, bukan hanya beralih, dan untuk beberapa huruf akan ada tugas untuk membalik bit lain yang lebih tinggi. (Dan operasi yang sama tidak dapat diaktifkan, dan memeriksa karakter alfabet di tempat pertama akan lebih sulit karena Anda tidak bisa|= 0x20
memaksa lcase.)Banyak jawaban bagus di sini yang menjelaskan cara kerjanya, tetapi mengapa cara ini bekerja adalah untuk meningkatkan kinerja. Pengoperasian bitwise lebih cepat daripada kebanyakan operasi lain dalam suatu prosesor. Anda dapat dengan cepat melakukan perbandingan case yang tidak sensitif dengan tidak melihat bit yang menentukan case atau mengubah case ke atas / bawah hanya dengan membalik bit (orang-orang yang mendesain tabel ASCII cukup pintar).
Jelas, ini bukan masalah besar hari ini seperti pada tahun 1960 (ketika pekerjaan pertama kali dimulai pada ASCII) karena prosesor yang lebih cepat dan Unicode, tetapi masih ada beberapa prosesor berbiaya rendah yang dapat membuat perbedaan yang signifikan selama Anda hanya bisa menjamin karakter ASCII.
https://en.wikipedia.org/wiki/Bitwise_operation
CATATAN: Saya akan merekomendasikan menggunakan perpustakaan standar untuk bekerja dengan string karena sejumlah alasan (keterbacaan, kebenaran, portabilitas, dll). Hanya gunakan sedikit membalik jika Anda telah mengukur kinerja dan ini adalah hambatan Anda.
sumber
Begitulah cara kerja ASCII, itu saja.
Tetapi dalam mengeksploitasi ini, Anda memberikan portabilitas karena C ++ tidak menuntut ASCII sebagai encoding.
Inilah sebabnya mengapa fungsi
std::toupper
danstd::tolower
diimplementasikan dalam pustaka standar C ++ - Anda harus menggunakannya.sumber
Lihat tabel kedua di http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii , dan catatan berikut, direproduksi di bawah:
ASCII dirancang sedemikian rupa sehingga tombol keyboard shiftdan ctrldapat diimplementasikan tanpa banyak (atau mungkin ada ctrl) logika - shiftmungkin diperlukan hanya beberapa gerbang. Mungkin masuk akal setidaknya untuk menyimpan protokol kawat seperti pengkodean karakter lainnya (tidak diperlukan konversi perangkat lunak).
Artikel yang ditautkan juga menjelaskan banyak konvensi peretas aneh seperti
And control H does a single character and is an old^H^H^H^H^H classic joke.
( ditemukan di sini ).sumber
foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20
, meskipun ini hanya ASCII dan karenanya tidak bijaksana karena alasan yang dinyatakan dalam jawaban lain. Mungkin juga dapat ditingkatkan dengan pemrograman bebas cabang.foo ^= 0x20 >> !(foo & 0x40)
akan lebih sederhana. Juga contoh bagus mengapa kode singkat sering dianggap tidak dapat dibaca ^ _ ^.Xoring dengan 32 (00100000 dalam biner) menetapkan atau mengatur ulang bit keenam (dari kanan). Ini sangat setara dengan menambah atau mengurangi 32.
sumber
Rentang alfabet huruf kecil dan huruf besar tidak melewati batas
%32
"alignment" dalam sistem pengkodean ASCII.Inilah sebabnya mengapa bit
0x20
adalah satu-satunya perbedaan antara versi huruf besar / kecil dari huruf yang sama.Jika ini bukan masalahnya, Anda harus menambah atau mengurangi
0x20
, bukan hanya beralih, dan untuk beberapa huruf akan ada tugas untuk membalik bit lain yang lebih tinggi. (Dan tidak akan ada satu operasi yang bisa beralih, dan memeriksa karakter alfabet di tempat pertama akan lebih sulit karena Anda tidak bisa | = 0x20 untuk memaksa lcase.)Trik khusus ASCII terkait: Anda dapat memeriksa karakter ASCII alfabet dengan memaksa huruf kecil dengan
c |= 0x20
dan kemudian memeriksa apakah (tidak ditandatangani)c - 'a' <= ('z'-'a')
. Jadi hanya 3 operasi: ATAU + SUB + CMP terhadap konstan 25. Tentu saja, kompiler tahu bagaimana mengoptimalkan(c>='a' && c<='z')
ke asm seperti ini untuk Anda , jadi paling-paling Anda harus melakukanc|=0x20
bagian sendiri. Agak merepotkan untuk melakukan sendiri semua pengecoran yang diperlukan, terutama untuk mengerjakan promosi bilangan bulat default yang akan ditandatanganiint
.Lihat juga Konversi String Dalam C ++ Ke Huruf Besar (string SIMD
toupper
hanya untuk ASCII, menutupi operand untuk XOR menggunakan centang itu.)Dan juga Cara mengakses array char dan mengubah huruf kecil ke huruf besar, dan sebaliknya (C dengan SIMD intrinsik, dan skalar x86 asm case-flip untuk karakter ASCII alfabet, membuat yang lain tidak dimodifikasi.)
Trik-trik ini sebagian besar hanya berguna jika tangan mengoptimalkan beberapa pemrosesan teks dengan SIMD (misalnya SSE2 atau NEON), setelah memeriksa bahwa tidak ada
char
dalam vektor memiliki bit set tinggi. (Dan dengan demikian tidak ada byte yang merupakan bagian dari multi-byte UTF-8 encoding untuk satu karakter, yang mungkin memiliki invers huruf besar / kecil yang berbeda). Jika Anda menemukannya, Anda dapat kembali ke skalar untuk potongan 16 byte ini, atau untuk sisa string.Bahkan ada beberapa lokal tempat
toupper()
atautolower()
pada beberapa karakter dalam rentang ASCII menghasilkan karakter di luar rentang itu, terutama Turki di mana saya ↔ ı dan İ ↔ i. Di lokasi tersebut, Anda memerlukan pemeriksaan yang lebih canggih, atau mungkin tidak mencoba menggunakan pengoptimalan ini sama sekali.Tetapi dalam beberapa kasus, Anda diizinkan untuk menggunakan ASCII alih-alih UTF-8, mis. Utilitas Unix with
LANG=C
(the POSIX locale), bukanen_CA.UTF-8
atau apa pun.Tetapi jika Anda bisa memastikan keamanannya, Anda dapat membuat
toupper
string berukuran sedang jauh lebih cepat daripada menelepontoupper()
dalam satu lingkaran (seperti 5x), dan terakhir saya uji dengan Boost 1.58 , jauh lebih cepat daripadaboost::to_upper_copy<char*, std::string>()
yang dilakukan bodohdynamic_cast
untuk setiap karakter.sumber