Bu kaynak kodu C'de bir dizgeyi değiştiriyor. Bunu nasıl yapıyor?


106

Bazı emülatör kodunu okuyorum ve gerçekten tuhaf bir şeye karşı çıktım:

switch (reg){
    case 'eax':
    /* and so on*/
}

Bu nasıl mümkün olabilir? Sadece switchintegral türlerinde yapabileceğini düşündüm . Devam eden bazı makro hileler var mı?


29
dize değildir 'eax've sabit tamsayı değerini numaralandırır
P__J__

12
Tek tırnak, çift değil. Bir karakter sabitine yükseltilir int, bu yüzden yasaldır. Ancak, çok karakterli bir sabitin değeri uygulama tanımlıdır, bu nedenle kod başka bir derleyicide beklendiği gibi çalışmayabilir. Örneğin, eaxolabilir 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, başka bir şey.
Davislor

2
@Davislor: "reg" değişkeninin adı verildiğinde ve eax'ın bir x86 yazmacı olduğu gerçeği, uygulama tanımlı davranışın tamam olması amaçlandığını tahmin ediyorum, çünkü kodda kullanıldığı her yerde aynı. 'eax' != 'ebx'Tabii ki olduğu sürece , örneklerinizden sadece bir veya ikisinde başarısız olur. Bununla birlikte, bir yerde gerçekte varsaydığı *(int*)("eax") == 'eax've bu nedenle örneklerinizin çoğunda başarısız olan bazı kodlar olabilir .
Steve Jessop

2
@SteveJessop Söylediklerinize katılmıyorum, ancak birisinin kodu aynı mimari için bile farklı bir derleyicide derlemeye çalışıp farklı davranışlar sergileyebileceği gerçek bir tehlike var. Örneğin, 'eax'eşittir 'ebx'veya ile karşılaştırabilir 'ax've switch ifadesi istendiği gibi çalışmayabilir.
Davislor

1
Kayıt türünün veri türünü yukarı baksaydınız / bize gösterseydiniz, tüm bu gizem çabucak ortadan kalkardı.
ths

Yanıtlar:


146

(Daha fazla kod yapıştırmadığınız sürece "makro hilesi" bölümüne yalnızca siz cevap verebilirsiniz. Ancak burada makroların üzerinde çalışacağı pek bir şey yoktur - resmi olarak anahtar kelimeleri yeniden tanımlamanıza izin verilmez ; bunu yapma davranışı tanımlanmamıştır.)

Programın okunabilirliğini sağlamak için, zeki geliştirici, uygulama tanımlı davranıştan yararlanıyor . 'eax'olduğu değil bir dize, ancak bir çok karakter değişmezi . Etrafındaki tek tırnak karakterlerini çok dikkatli bir şekilde not edin eax. Büyük olasılıkla int, sizin durumunuzda bu karakter kombinasyonuna özgü bir karakter veriyor. (Çoğu zaman her karakter 32 bitte 8 bit kaplar int). Ve herkes switchbir üzerinde yapabileceğini biliyor int!

Son olarak, standart bir referans:

C99 standardı şöyle diyor:

6.4.4.4p10: "Birden fazla karakter içeren (örn. 'Ab') veya tek baytlık bir yürütme karakteriyle eşleşmeyen bir karakter veya kaçış dizisi içeren bir tamsayı karakter sabitinin değeri, uygulama tanımlıdır. "


55
Herhangi birinin bunu görmesi ve paniğe kapılması durumunda, derleyiciniz tarafından uygun bir şekilde çalışması ve belgelenmesi için "uygulama tanımlı" gerekir (standart, davranışın sezgisel olmasını veya belgelerin herhangi bir iyi olmasını gerektirmez, ancak ...). Bu, "tanımlanmamış" ın aksine, yazdıklarını tam olarak anlayan bir kodlayıcı için "güvenli" dir.
Leushenko

7
@Justin Olabilse de, bu oldukça ters olurdu. Cevabın büyük olasılıkla önerdiği şeyi yapmazsa, bir sonraki olasılık muhtemelen sadece ilk karakteri kullanması ve geri kalanını görmezden gelmesidir.
Barmar

5
@ZanLynx Olumlu değilim, ancak bu özelliğin Unicode ve diğer MBCS standartlarından çok daha önce olduğuna inanıyorum. Bellek dökümlerindeki metin gibi görünen "sihirli sayılar" ve RIFF tarzı dosya biçimi yığın kimlikleri bildiğim ilk uygulamalardı.
Russell Borogove

16
@ jpmc26 Bu tanımsız bir davranış değil, uygulama tanımlı. Derleyici dokümantasyonu iblislerden bahsetmedikçe burnunuz güvende.
barmar

7
@ZanLynx: Korkarım asıl amaç Unicode, UTF-8 ve herhangi bir çokbaytlı karakter kodlamasından neredeyse 20 yıl öncesine dayanıyor. çok karakterli sabit 2, 3 veya 4 baytlık grupları temsil eden tam sayıları ifade etmenin kullanışlı bir yoluydu (bayt ve int boyutlarına bağlı olarak). Uygulamalar ve mimariler arasındaki tutarsızlıklar, komitenin bunu uygulama tanımlı olarak ilan etmesine neden oldu, bu da 'ab'başlangıç 'a've bitiş değerini hesaplamanın taşınabilir bir yolu olmadığı anlamına geliyor 'b'.
chqrlie

45

C Standardına göre (6.8.4.2 Switch ifadesi)

3 Her durum etiketinin ifadesi bir tamsayı sabit ifadesi olmalıdır ...

ve (6.6 Sabit ifadeler)

6 Bir tamsayı sabit ifadesi tamsayı türüne sahip olmalıdır ve yalnızca tamsayı sabitleri, numaralandırma sabitleri, karakter sabitleri , sonuçları tamsayı sabitleri olan ifadelerin boyutu ve yayınların anlık işlenenleri olan kayan sabitler olan işlenenlere sahip olacaktır. Bir tamsayı sabiti ifadesindeki çevrim operatörleri, bir işlenenin sizeof operatörünün parçası olması dışında, yalnızca aritmetik türleri tam sayı türlerine dönüştürecektir.

Şimdi nedir 'eax'?

C Standardı (6.4.4.4 Karakter sabitleri)

2 Bir tamsayı karakter sabiti, 'x' gibi tek tırnak içine alınmış bir veya daha fazla çok baytlı karakter dizisidir ...

Yani 'eax'bir tamsayı karakter sabiti aynı bölümün 10. paragrafı uyarınca olan

  1. ... Birden fazla karakter içeren (örneğin, 'ab') veya tek baytlık bir yürütme karakteriyle eşleşmeyen bir karakter veya kaçış dizisi içeren bir tamsayı karakter sabitinin değeri, uygulama tanımlıdır.

Dolayısıyla, bahsedilen ilk alıntıya göre, bir durum etiketi olarak kullanılabilen bir tamsayı sabit ifadesinin bir işleneni olabilir.

Bir karakter sabitinin (tek tırnak içine alınmış) türe intsahip olduğuna ve bir karakter dizisi türüne sahip bir dize değişmezi (çift tırnak içine alınmış bir karakter dizisi) ile aynı olmadığına dikkat edin.


12

Diğerlerinin de söylediği gibi, bu bir intsabittir ve gerçek değeri uygulama tanımlıdır.

Kodun geri kalanının şuna benzediğini varsayıyorum:

if (SOMETHING)
    reg='eax';
...
switch (reg){
    case 'eax':
    /* and so on*/
}

İlk bölümdeki 'eax'ın ikinci bölümdeki' eax 'ile aynı değere sahip olduğundan emin olabilirsiniz, yani her şey yolunda, değil mi? ... yanlış.

Bir yorumda @Davislor, 'eax' için bazı olası değerleri listeler:

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, veya başka bir şey

İlk potansiyel değeri fark ettiniz mi? Yani, 'e'diğer iki karakteri görmezden gelmek. Sorun, programın muhtemelen kullandığı 'eax', 'ebx'vb. Tüm bu sabitler, 'e'sonuçta elde ettiğiniz gibi aynı değere sahipse

switch (reg){
    case 'e':
       ...
    case 'e':
       ...
    ...
}

Bu pek iyi görünmüyor, değil mi?

"Gerçekleştirme tanımlı" nın iyi yanı, programcının derleyicisinin belgelerini kontrol edebilmesi ve bu sabitlerle mantıklı bir şey yapıp yapmadığını görebilmesidir. Varsa, ev bedava.

Kötü yanı, başka bir zavallı arkadaşın kodu alıp başka bir derleyiciyi kullanarak derlemeye çalışabilmesidir. Anında derleme hatası. Program taşınabilir değil.

@Zwol'un yorumlarda belirttiği gibi durum düşündüğüm kadar kötü değil, kötü durumda kod derlenmiyor. Bu, en azından size sorun için tam bir dosya adı ve satır numarası verecektir. Yine de bir çalışma programınız olmayacak.


1
çeşit dışındaki assert('eax' != 'ebx'); //if this fails you can't compile the code because...asıl yazar> tamamen yapısını değiştirmeden diğer derleyici hatalarını önlemek için yapabileceği orada bir şey
Dan Is Fiddling tarafından Firelight

6
Aynı değere sahip iki durum etiketi bir kısıtlama ihlalidir (6.8.4.2p3: "... aynı switch deyimindeki iki durum sabiti ifadesi dönüşümden sonra aynı değere sahip olmayacaktır") bu nedenle, tüm kodlar bu sabitlerin değerlerini opak olarak ele alır, bunun ya çalışması ya da derlenememesi garantilidir.
zwol

Daha kötüsü, başka bir derleyicide derleme yapan zayıf arkadaşın muhtemelen herhangi bir derleme zamanı hatası görmeyeceğidir (ints'i açmak iyidir); bunun yerine, çalışma zamanı hataları ortaya çıkacak ...
tucuxi

1

Kod parçası, çok karakterli karakter sabiti adı verilen ve aynı zamanda çoklu karakter olarak da adlandırılan tarihsel bir tuhaflık kullanır .

'eax' değeri uygulama tanımlı olan bir tamsayı sabitidir.

İşte çoklu karakterlerle ilgili ilginç bir sayfa ve nasıl kullanılabilecekleri, ancak kullanılmaması gerekenler:

http://www.zipcon.net/~swhite/docs/computers/languages/c_multi-char_const.html


Dikiz aynasından daha uzağa baktığımızda, eski güzel günlerden Dennis Ritchie tarafından hazırlanan orijinal C kılavuzu ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) karakter sabitlerini nasıl belirlediğini burada görebilirsiniz. .

2.3.2 Karakter sabitleri

Bir karakter sabiti, tek tırnak içine alınmış 1 veya 2 karakterdir '' '''. Bir karakter sabiti içinde tek bir alıntıdan önce ters eğik çizgi '' \'' gelmelidir . Grafik olmayan bazı karakterler ve '' \'' aşağıdaki tabloya göre atlanabilir:

    BS \b
    NL \n
    CR \r
    HT \t
    ddd \ddd
    \ \\

Çıkış '' \ddd'' ters eğik çizgiyi takip eden 1, 2 veya 3 sekizlik basamaktan oluşur ve bunlar istenen karakterin değerini belirtmek için alınır. Bu yapının özel bir durumu, \0bir boş karakteri belirten '' '' (bir rakam gelmez) 'dir.

Karakter sabitleri tam olarak tamsayılar gibi davranır (özellikle karakter türündeki nesneler gibi değil). PDP-11'in adresleme yapısına uygun olarak, 1 uzunluğundaki bir karakter sabiti, düşük sıralı baytta verilen karakter için koda ve yüksek sıralı baytta 0'a sahiptir; 2 uzunluğundaki bir karakter sabiti, düşük bayttaki ilk karakter için ve yüksek sıralı bayttaki ikinci karakter için olan koda sahiptir. Birden fazla karakter içeren karakter sabitleri, doğası gereği makineye bağlıdır ve bundan kaçınılmalıdır.

Son cümle, bu ilginç yapı hakkında hatırlamanız gereken tek şey: Birden fazla karaktere sahip karakter sabitleri, doğası gereği makineye bağlıdır ve bundan kaçınılmalıdır.

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.