Küçük harfleri büyük harflere ve tersine çeviren ^ = 32'nin ardındaki fikir nedir?


146

Kod güçlerinde bazı problemleri çözüyordum. Normalde önce karakterin büyük veya küçük İngilizce harf olup olmadığını kontrol ederim, sonra 32onu karşılık gelen harfe dönüştürmek için çıkarırım veya eklerim . Ama ^= 32aynı şeyi yapacak birini buldum . İşte burada:

char foo = 'a';
foo ^= 32;
char bar = 'A';
bar ^= 32;
cout << foo << ' ' << bar << '\n'; // foo is A, and bar is a

Bunun için bir açıklama aradım ve bulamadım. Peki bu neden işe yarıyor?



112
FWIW, gerçekten "işe yaramıyor". Bu özel karakter seti için çalışır ama sen kullanmalıdır olmaz diğer setleri vardır toupperve tolowerdavaları geçmek için.
NathanOliver

7
bazen çevrimiçi yarışmalarda "fikir", ciddi bir incelemeden asla geçmeyecek kadar karmaşık bir şekilde kod
yazmaktır

21
^ = XOR kullanarak değeri dönüştürüyor. Büyük harf ASCII harflerinin karşılık gelen bitinde sıfır bulunurken, küçük harflerin bir tane vardır. Bu, lütfen yapma dedi! Küçük ve büyük harf arasında dönüştürme yapmak için uygun karakter (unicode) rutinlerini kullanın. Sadece ASCII çağı çoktan geride kaldı.
Hans-Martin Mosner

14
Sadece bazı karakter setleriyle çalıştığı için değil. Hepimizin varsaysak bile, dünya UTF-8 (en azından güzel bir ütopik hedefi olabilir), o da sadece 26 harf ile çalışır olduğunu Aiçin Z. Sadece İngilizce'yi önemsediğiniz sürece (ve "naif" yazımları, "café" gibi kelimeleri veya aksanlı isimleri kullanmadığınız sürece ...) sorun değil, ama dünya sadece İngilizce değil.
ilkkachu

Yanıtlar:


149

İkili ASCII kod tablosuna bir göz atalım.

A 1000001    a 1100001
B 1000010    b 1100010
C 1000011    c 1100011
...
Z 1011010    z 1111010

Ve 32, 0100000küçük ve büyük harfler arasındaki tek farktır. Yani bu biti değiştirmek, bir harfin durumunu değiştirir.


49
"durumu değiştirir" * yalnızca ASCII için
Mooing Duck

39
@ ASCII'de yalnızca A-Za-z için @Mooing. Küçük harf "[", "{" değildir .
dbkk

21
@dbkk {daha kısadır [, bu yüzden "küçük" bir harftir . Hayır? Tamam, kendimi göstereceğim: D
Peter Badida

25
Trivia bir ayrıntı: 7 bitlik alanında, Alman bilgisayarlar vardı [] {|} daha o karakterden daha Sesli harfler gerekli beri çok bu bağlamda, ÄÖÜäöü remapped, {(ä) aslında idi küçük [(Ä).
Guntram Blohm

14
@GuntramBlohm Daha fazla önemsiz şey, bu nedenle IRC sunucuları , takma adlar büyük / küçük harfe duyarlı olmadığından ve IRC'nin kökenleri İskandinavya'da olduğundan IRC sunucularının aynı takma adları dikkate almasının foobar[]ve foobar{}bu takma adlar olarak kabul edilmesinin nedeni budur :)
ZeroKnight

117

Bu, ASCII değerlerinin gerçekten zeki insanlar tarafından seçildiğinden daha gerçeği kullanır.

foo ^= 32;

Bu 6. düşük bit çevirir 1 arasında foobir alt kılıf ve bir ASCII harf transforme, (ASCII tür büyük bayrak) tersi .

+---+------------+------------+
|   | Upper case | Lower case |  32 is 00100000
+---+------------+------------+
| A | 01000001   | 01100001   |
| B | 01000010   | 01100010   |
|            ...              |
| Z | 01011010   | 01111010   |
+---+------------+------------+

Misal

'A' ^ 32

    01000001 'A'
XOR 00100000 32
------------
    01100001 'a'

Ve XOR mülkiyetine göre 'a' ^ 32 == 'A',.

Farkına varmak

Karakterleri temsil etmek için ASCII kullanmak için C ++ gerekli değildir. Diğer bir varyant ise EBCDIC'dir . Bu numara yalnızca ASCII platformlarında çalışır. Daha taşınabilir bir çözüm kullanmak std::tolowerve std::touppersunulan bonusla birlikte yerel ayarların farkında olmaktır (tüm sorunlarınızı otomatik olarak çözmez, yorumlara bakın):

bool case_incensitive_equal(char lhs, char rhs)
{
    return std::tolower(lhs, std::locale{}) == std::tolower(rhs, std::locale{}); // std::locale{} optional, enable locale-awarness
}

assert(case_incensitive_equal('A', 'a'));

1) 32 1 << 5(2'den 5'e) olduğu gibi, 6. biti çevirir (1'den sayarak).


16
EBCDIC de bazı çok zeki insanlar tarafından seçildi: delikli kartlarda gerçekten güzel çalışıyor, bkz. Bir karmaşa olan ASCII. Ama bu güzel bir cevap, +1.
Bathsheba

65
Ben yumruk kartları hakkında bilmiyorum, ama ASCII edildi kağıt kasete kullandı. Sil karakterinin 1111111 olarak kodlanmasının nedeni budur: Böylece herhangi bir karakteri kasetteki sütunundaki tüm delikleri açarak "silinmiş" olarak işaretleyebilirsiniz.
dan04

23
@Bathsheba, delikli kart kullanmamış biri olarak, EBCDIC'in akıllıca tasarlandığı fikrine kafamı sarmak çok zor.
Lord Farquaad

9
@LordFarquaad IMHO Harflerin bir delikli kart üzerine nasıl yazıldığını gösteren Wikipedia resmi, EBCDIC'in bu kodlama için bazı (ancak toplam değil, bkz. en.wikipedia.org/wiki/EBCDIC#/media/…
Peteris

11
@ dan04 "'MASSE'in küçük harfli hali nedir?" Bilmeyenler için Almanca'da büyük harfli MASSE olan iki kelime vardır ; biri "Masse" ve diğeri "Maße". tolowerAlmancaya uygun olmak sadece bir sözlüğe ihtiyaç duymaz, anlamı ayrıştırabilmelidir.
Martin Bonner Monica'yı

35

Bunun - akıllı görünmesine rağmen - gerçekten, gerçekten aptalca bir hack olduğunu söylememe izin verin. 2019'da birisi size bunu tavsiye ederse, ona vurun. Olabildiğince sert vur.
Elbette, İngilizceden başka bir dili asla kullanmayacağınızı biliyorsanız, sizin ve başka hiç kimsenin kullanmadığı kendi yazılımınızda yapabilirsiniz. Aksi takdirde, gitme.

Hack bilgisayarlar yoktu zaman gerçekten bazı 30-35 yıl önce "Tamam" tartışılabilir oldu ASCII çok ama İngilizce yapmak ve belki bir veya iki büyük Avrupa dilinden . Ama ... artık öyle değil.

Hack işe yarıyor çünkü ABD-Latin büyük ve küçük harfleri birbirinden tamamen 0x20ayrı ve aynı sırada görünüyor ki bu sadece bir parça fark. Aslında, bu biraz hack, değişiyor.

Şimdi, Batı Avrupa ve daha sonra Unicode konsorsiyumu için kod sayfaları oluşturan insanlar, bu şemayı örneğin Almanca Ümleutları ve Fransız aksanlı Ünlüler için koruyacak kadar akıllıydı. Böyle değil (birisi 2017'de Unicode konsorsiyumunu ikna edene ve büyük bir Fake News basılı dergisi bunun hakkında yazdı, aslında Duden'i ikna edene kadar - bu konuda yorum yok ) bir versal olarak bile mevcut değil (SS'ye dönüşüyor) . Şimdi yok olarak versal mevcut olmakla ikisidir 0x1DBFdışında pozisyonları değil 0x20.

Uygulamacılarıdır Ancak vardı değil böyle devam edin için düşünceli yeterli. Örneğin, hackinizi bazı Doğu Avrupa dillerinde veya benzerlerinde uygularsanız (Kiril alfabesini bilmem), kötü bir sürprizle karşılaşacaksınız. Tüm bu "balta" karakterleri bunun örnekleridir, küçük ve büyük harf birbirinden ayrıdır. Kesmek ve böylece yok değil düzgün orada çalışmak.

Göz önünde bulundurulması gereken çok şey var, örneğin, bazı karakterler basitçe küçükten büyük harfe dönüşmez (farklı dizilerle değiştirilirler) veya form değiştirebilirler (farklı kod noktaları gerektirir).

Bu saldırının Tayca veya Çince gibi şeylere ne yapacağını düşünmeyin bile (bu size tamamen saçmalık verecektir).

Birkaç yüz CPU döngüsünü kaydetmek 30 yıl önce çok değerli olabilirdi, ancak günümüzde bir dizeyi düzgün bir şekilde dönüştürmek için hiçbir mazeret yok. Bu önemsiz olmayan görevi yerine getirmek için kütüphane işlevleri vardır.
Birkaç düzinelerce kilobaytlık metni düzgün bir şekilde dönüştürmek için geçen süre günümüzde göz ardı edilebilir.


2
Tamamen katılıyorum - her programcı için neden işe yaradığını bilmek iyi bir fikir olsa da - iyi bir mülakat sorusu bile olabilir .. Bu ne işe yarar ve ne zaman kullanılmalıdır :)
Bill K

33

Çalışır çünkü ASCII'deki 'a' ve A 'arasındaki fark ile türetilmiş kodlamalar arasındaki fark 32'dir ve 32 aynı zamanda altıncı bitin değeridir. 6. biti özel bir OR ile çevirmek böylece üst ve alt arasında dönüşüm sağlar.


22

Büyük olasılıkla karakter setini uygulamanız ASCII olacaktır. Tabloya bakarsak:

görüntü açıklamasını buraya girin

32Küçük harfli sayı ile büyük harfli sayı arasında bir fark olduğunu görüyoruz . Bu nedenle, yaparsak^= 32 (en az önemli 6. biti değiştirmeye eşittir), küçük harf ve büyük harf karakter arasında değişir.

Sadece harflerle değil tüm sembollerle çalıştığını unutmayın. 6. bitin farklı olduğu ilgili karaktere sahip bir karakter arasında geçiş yaparak, aralarında ileri geri hareket eden bir karakter çifti ortaya çıkarır. Harfler için, ilgili büyük / küçük harf karakterleri böyle bir çift oluşturur. A NULdeğişecek Spaceve @tersi olacak ve ters işaret ile geçişler olacaktır. Temelde, bu grafikteki ilk sütundaki herhangi bir karakter, bir sütunun üstündeki karakterle geçiş yapar ve aynı şey üçüncü ve dördüncü sütunlar için de geçerlidir.

Herhangi bir sistemde çalışacağının garantisi olmadığı için bu hack'i kullanmazdım. Sadece kullanmak ToUpper ve tolower yerine ve gibi sorgular isupper .


2
32 farkı olan tüm harflerde işe yaramıyor. Aksi takdirde '@' ve '' arasında çalışacaktı!
Matthieu Brucher

2
@MatthieuBrucher Çalışıyor, 32 ^ 320, 64 değil
NathanOliver

5
"@" ve "" harf "değildir. Sadece [a-z]ve [A-Z]"harf" dir. Gerisi aynı kuralı izleyen tesadüflerdir. Biri sizden "büyük harf]" isteseydi, bu ne olurdu? yine de "]" olacaktır - "}", "]" nin "büyük harfi" değildir.
Freedomn-m

4
@MatthieuBrucher: Bu noktayı belirtmenin bir başka yolu da, küçük harfli ve büyük harfli alfabetik aralıkların %32ASCII kodlama sistemindeki "hizalama" sınırını geçmemesidir . Bu nedenle bit 0x20, aynı harfin büyük / küçük harf versiyonları arasındaki tek farktır. Eğer durum bu değilse, 0x20sadece geçiş yapmakla kalmayıp, toplama veya çıkarma yapmanız gerekir ve bazı harfler için diğer yüksek bitleri çevirmek için gerçekleştirme işlemi olur. (Ve aynı işlem geçiş yapamazdı ve ilk etapta alfabetik karakterleri kontrol etmek daha zor olurdu çünkü |= 0x20lcase'i zorlayamazsınız.)
Peter Cordes

2
+1 asciitable.com'a son 15 ya da 20 yıldır tam olarak o grafiğe (ve genişletilmiş ASCII sürümüne !!) bakmak için yapılan tüm ziyaretleri hatırlattığı için?
AC

15

Burada bunun nasıl çalıştığını açıklayan pek çok iyi yanıt var, ancak neden bu şekilde çalıştığını, performansı artırmak için. Bitsel işlemler, bir işlemci içindeki diğer işlemlerin çoğundan daha hızlıdır. Büyük / küçük harf duyarlılığını belirleyen bitlere bakmayarak veya sadece biti çevirerek büyük / küçük harf değiştirerek hızlı bir şekilde büyük / küçük harf duyarlı bir karşılaştırma yapabilirsiniz (ASCII tablosunu tasarlayanlar oldukça zekiydi).

Açıkçası, bu, daha hızlı işlemciler ve Unicode nedeniyle 1960'ta (ASCII'de ilk çalışma başladığında) olduğu kadar bugün neredeyse bir anlaşma değil, ancak yine de önemli bir fark yaratabilecek bazı düşük maliyetli işlemciler var. sadece ASCII karakterlerini garanti edebildiğiniz sürece.

https://en.wikipedia.org/wiki/Bitwise_operation

Basit düşük maliyetli işlemcilerde, tipik olarak, bitsel işlemler bölmeden önemli ölçüde daha hızlıdır, çarpmadan birkaç kat daha hızlıdır ve bazen eklemeden önemli ölçüde daha hızlıdır.

NOT: Çeşitli nedenlerle (okunabilirlik, doğruluk, taşınabilirlik, vb.) Dizelerle çalışmak için standart kitaplıkları kullanmanızı tavsiye ederim. Yalnızca performansı ölçtüyseniz ve bu sizin darboğazınızsa bit çevirmeyi kullanın.


14

ASCII böyle çalışır, hepsi bu.

Ancak bundan yararlanarak, C ++ kodlama olarak ASCII'de ısrar etmediği için taşınabilirlikten vazgeçiyorsunuz .

İşlevlerin std::toupperve std::tolowerC ++ standart kitaplığında uygulanmasının nedeni budur - yerine bunları kullanmalısınız.


6
DNS gibi ASCII'nin kullanılmasını gerektiren protokoller de vardır. Aslında, "0x20 numarası" bazı DNS sunucuları tarafından bir DNS sorgusuna bir anti-spoofing mekanizması olarak ek entropi eklemek için kullanılır. DNS, büyük / küçük harfe duyarlı değildir, ancak aynı zamanda büyük / küçük harf korumalı olması gerekir; bu nedenle, rasgele bir büyük / küçük harf içeren bir sorgu gönderir ve aynı durumu geri alırsanız, yanıtın üçüncü bir taraf tarafından sahte olmadığının iyi bir göstergesidir.
Alnitak

Bir çok kodlamanın standart (uzatılmamış) ASCII karakterleri için hala aynı gösterime sahip olduğunu belirtmekte fayda var. Ancak yine de, farklı kodlamalar konusunda gerçekten endişeleniyorsanız, uygun işlevleri kullanmalısınız.
Captain Man

5
@CaptainMan: Kesinlikle. UTF-8 çok güzel bir şeydir. IEEE754'ün kayan nokta için sahip olduğu ölçüde C ++ standardına "emilir".
Bathsheba

11

Http://www.catb.org/esr/faqs/things-every-hacker-once-knew/#_ascii adresindeki ikinci tabloya ve aşağıda çoğaltılan aşağıdaki notlara bakın:

Klavyenizdeki Kontrol değiştiricisi, temelde yazdığınız her karakterin ilk üç bitini temizler, alttaki beşi bırakır ve onu 0..31 aralığına eşler. Yani, örneğin, Ctrl-SPACE, Ctrl- @ ve Ctrl-`'nin tümü aynı anlama gelir: NUL.

Çok eski klavyeler, tuşa bağlı olarak yalnızca 32 veya 16 bit arasında geçiş yaparak Shift'i kullanırlardı; ASCII'deki küçük ve büyük harfler arasındaki ilişkinin bu kadar düzenli olmasının ve sayılar ve semboller ile bazı sembol çiftleri arasındaki ilişkinin gözlerinizi kısarsanız biraz normal olmasının nedeni budur. Tamamen büyük harfli bir terminal olan ASR-33, 16 biti kaydırarak anahtarları olmayan bazı noktalama karakterlerini oluşturmanıza bile izin verir; böylece, örneğin, Shift-K (0x4B) bir [(0x5B) oldu

ASCII, shiftve ctrlklavye tuşları çok fazla (veya belki de herhangi bir ctrlmantık olmadan) uygulanabilecek şekilde tasarlandı - shiftmuhtemelen yalnızca birkaç kapı gerektiriyordu. Muhtemelen kablo protokolünü saklamak diğer karakter kodlamaları kadar mantıklıydı (yazılım dönüşümü gerekmez).

Bağlantılı makale ayrıcaAnd control H does a single character and is an old^H^H^H^H^H classic joke. ( burada bulunan ) gibi birçok garip hacker sözleşmesini de açıklamaktadır .


1
Daha fazla ASCII w / için bir kaydırma geçişi uygulayabilir foo ^= (foo & 0x60) == 0x20 ? 0x10 : 0x20, ancak bu yalnızca ASCII'dir ve bu nedenle diğer yanıtlarda belirtilen nedenlerden dolayı akıllıca değildir. Muhtemelen dalsız programlama ile de geliştirilebilir.
Iiridayn

1
Ah, foo ^= 0x20 >> !(foo & 0x40)daha basit olurdu. Ayrıca kısa kodun neden genellikle okunamaz olarak kabul edildiğine dair iyi bir örnek ^ _ ^.
Iiridayn

8

32 (ikili olarak 00100000) ile Xoring, altıncı biti (sağdan) ayarlar veya sıfırlar. Bu kesinlikle 32 eklemeye veya çıkarmaya eşdeğerdir.


2
Bunu söylemenin başka bir yolu, XOR'un taşıma olmadan ekleme olduğudur.
Peter Cordes

7

Küçük harfli ve büyük harfli alfabetik aralıklar %32ASCII kodlama sisteminde bir "hizalama" sınırını geçmez .

Bu yüzden biraz 0x20 , aynı harfin büyük / küçük harf sürümleri arasındaki tek farktır.

Eğer durum bu değilse, 0x20sadece geçiş yapmakla kalmayıp, toplama veya çıkarma yapmanız gerekir ve bazı harfler için diğer yüksek bitleri çevirmek için gerçekleştirme işlemi olur. (Geçiş yapabilecek tek bir işlem olmazdı ve ilk etapta alfabetik karakterleri kontrol etmek daha zor olurdu çünkü lcase'i zorlamak için | = 0x20 yapamazsınız.)


Yalnızca ASCII ile ilgili hileler: ile küçük harfe zorlayarak ve sonra (işaretsiz) olup olmadığını kontrol ederek alfabetik bir ASCII karakterinic |= 0x20 kontrol edebilirsiniz c - 'a' <= ('z'-'a'). Yani sadece 3 işlem: VEYA + SUB + CMP sabit 25'e karşı. Elbette, derleyiciler (c>='a' && c<='z') sizin için bu şekilde asm'i nasıl optimize edeceklerini biliyorlar , bu yüzden en fazla c|=0x20kısmı kendiniz yapmalısınız . Özellikle imzalanacak varsayılan tamsayı promosyonları etrafında çalışmak için gerekli tüm dökümleri kendiniz yapmak oldukça sakıncalıdır int.

unsigned char lcase = y|0x20;
if (lcase - 'a' <= (unsigned)('z'-'a')) {   // lcase-'a' will wrap for characters below 'a'
    // c is alphabetic ASCII
}
// else it's not

Ayrıca bkz . C ++ 'daki Dizeyi Büyük Harfe Dönüştürme ( toupperyalnızca ASCII için SIMD dizesi , bu denetimi kullanarak XOR için işleneni maskeleme.)

Ayrıca bir karakter dizisine nasıl erişilir ve küçük harfleri büyük harfe nasıl değiştirilir ve bunun tersi de geçerlidir (SIMD içselleri ile C ve alfabetik ASCII karakterler için skaler x86 asm büyük / küçük harf çevirme, diğerlerini değiştirmeden bırakarak.)


Bu hileler çoğunlukla yalnızca SIMD ile bazı metin işlemeyi (örn. SSE2 veya NEON) elle optimize ediyorsanız yararlıdır. char , bir vektördeki yüksek bit setine sahip . (Ve bu nedenle baytların hiçbiri, farklı büyük / küçük harf tersleri olabilecek tek bir karakter için çok baytlı UTF-8 kodlamasının parçası değildir). Herhangi birini bulursanız, bu 16 baytlık yığın için veya dizenin geri kalanı için skalere geri dönebilirsiniz.

Orada bazı yerel ayarlar bile vardır toupper()ya tolower(), özellikle Türk ı ı ↔ o aralığın dışında ASCII aralığı üretmek karakterler bazı karakterler ve İ ↔ i. Bu yerel ayarlarda, daha karmaşık bir kontrole ihtiyacınız olacak veya muhtemelen bu optimizasyonu hiç kullanmaya çalışmayacaksınız.


Ancak bazı durumlarda, UTF-8 yerine ASCII'yi varsaymanıza izin verilir, örneğin Unix yardımcı programları LANG=C(POSIX yerel ayarı), değil en_CA.UTF-8veya her neyse.

Ancak güvenli olduğunu doğrulayabilirseniz, toupperorta uzunlukta dizeleri toupper()bir döngü içinde aramadan çok daha hızlı yapabilirsiniz (5x gibi) ve son olarak Boost 1.58 ile test ettim , bu her karakter için aptalca olana göre çok daha hızlı .boost::to_upper_copy<char*, std::string>()dynamic_cast

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.