Kode sumber ini mengaktifkan string di C. Bagaimana cara melakukannya?

106

Saya membaca beberapa kode emulator dan saya telah membalas sesuatu yang sangat aneh:

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

Bagaimana ini mungkin? Saya pikir Anda hanya bisa switchpada tipe integral. Apakah ada beberapa tipuan makro yang sedang terjadi?

Ian Colton
sumber
29
itu bukan string 'eax'dan itu menghitung nilai integer konstan
P__J__
12
Tanda kutip tunggal, bukan ganda. Konstanta karakter dipromosikan int, jadi legal. Namun, nilai konstanta multi-karakter ditentukan oleh implementasi, jadi kode mungkin tidak berfungsi seperti yang diharapkan pada compiler lain. Sebagai contoh, eaxmungkin 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, atau sesuatu yang lain.
Davislor
2
@Davislor: diberi nama variabel "reg", dan fakta bahwa eax adalah register x86, saya akan menebak bahwa perilaku yang ditentukan implementasi dimaksudkan untuk menjadi OK, karena itu sama di mana pun itu digunakan dalam kode. Asalkan 'eax' != 'ebx', tentu saja, itu hanya gagal satu atau dua contoh Anda. Meskipun mungkin ada beberapa kode di suatu tempat yang berlaku *(int*)("eax") == 'eax', dan karena itu gagal sebagian besar contoh Anda.
Steve Jessop
2
@SteveJessop Saya tidak setuju dengan apa yang Anda katakan, tetapi ada bahaya nyata bahwa seseorang dapat mencoba mengkompilasi kode pada kompilator yang berbeda, bahkan untuk arsitektur yang sama, dan mendapatkan perilaku yang berbeda. Misalnya, 'eax'mungkin membandingkan sama dengan 'ebx'atau dengan 'ax', dan pernyataan sakelar tidak akan berfungsi sebagaimana mestinya.
Davislor
1
Semua misteri itu akan cepat terhapus jika Anda telah mencari / menunjukkan kepada kami tipe data reg.
ths

Jawaban:

146

(Hanya Anda yang dapat menjawab bagian "tipuan makro" - kecuali jika Anda menempelkan lebih banyak kode. Tapi tidak banyak yang bisa dilakukan makro di sini - secara formal Anda tidak diizinkan untuk mendefinisikan ulang kata kunci ; perilaku dalam melakukan itu tidak ditentukan.)

Untuk mencapai keterbacaan program, pengembang yang cerdas mengeksploitasi perilaku implementasi yang ditentukan . 'eax'adalah tidak string, tapi multi-karakter konstan . Perhatikan dengan cermat karakter kutipan tunggal di sekitar eax. Kemungkinan besar itu memberi Anda intdalam kasus Anda yang unik untuk kombinasi karakter itu. (Seringkali setiap karakter menempati 8 bit dalam 32 bit int). Dan semua orang tahu Anda bisa switchdi int!

Terakhir, referensi standar:

Standar C99 mengatakan:

6.4.4.4p10: "Nilai konstanta karakter bilangan bulat yang berisi lebih dari satu karakter (misalnya, 'ab'), atau berisi karakter atau urutan escape yang tidak memetakan ke karakter eksekusi byte tunggal, ditentukan oleh implementasi. "

Batsyeba
sumber
55
Untuk berjaga-jaga jika ada yang melihatnya dan panik, "implementasi-ditentukan" diperlukan untuk bekerja dan didokumentasikan oleh kompilator Anda dengan cara yang sesuai (standar tidak mengharuskan perilaku intuitif atau dokumentasinya bagus, tetapi ...). Ini "aman" digunakan untuk pembuat kode yang memahami sepenuhnya apa yang mereka tulis, bukan "tidak ditentukan".
Leushenko
7
@Justin Meskipun bisa, itu akan sangat menyimpang. Jika ia tidak melakukan apa yang kemungkinan besar disarankan oleh jawaban, kemungkinan berikutnya adalah ia hanya menggunakan karakter pertama dan mengabaikan yang lainnya.
Barmar
5
@ZanLynx Saya tidak yakin, tapi saya yakin fitur ini lama mendahului Unicode dan standar MBCS lainnya. "Angka ajaib" yang terlihat seperti teks dalam dump memori dan ID potongan format file gaya RIFF adalah aplikasi pertama yang saya ketahui.
Russell Borogove
16
@ jpmc26 Ini bukanlah perilaku tidak terdefinisi, ini adalah implementasi-nya. Jadi, kecuali dokumentasi penyusun menyebutkan setan, hidung Anda aman.
Barmar
7
@ZanLynx: Saya khawatir maksud aslinya mendahului Unicode, UTF-8 dan pengkodean karakter multibyte hampir 20 tahun. konstanta multi-karakter hanyalah cara praktis untuk mengekspresikan integer yang mewakili grup 2, 3, atau 4 byte (tergantung pada ukuran byte dan int). Inkonsistensi di seluruh implementasi dan arsitektur membuat komite menyatakan ini sebagai implementasi yang ditentukan , yang berarti tidak ada cara portabel untuk menghitung nilai 'ab'dari 'a'dan 'b'.
chqrlie
45

Menurut Standar C (6.8.4.2 Pernyataan sakelar)

3 Ekspresi setiap label kasus harus berupa ekspresi konstanta integer ...

dan (6.6 Ekspresi konstan)

6 Ekspresi konstanta integer harus memiliki tipe integer dan hanya boleh memiliki operan yang merupakan konstanta integer, konstanta enumerasi, konstanta karakter , ukuran ekspresi yang hasilnya adalah konstanta integer, dan konstanta mengambang yang merupakan operan langsung dari cast. Operator cor dalam ekspresi konstanta bilangan bulat hanya akan mengonversi jenis aritmatika ke jenis bilangan bulat, kecuali sebagai bagian dari operan ke ukuran operator.

Sekarang apa 'eax'?

Standar C (6.4.4.4 Konstanta karakter)

2 Konstanta karakter integer adalah urutan dari satu atau lebih karakter multibyte yang diapit tanda kutip tunggal , seperti dalam 'x' ...

Jadi 'eax'adalah konstanta karakter integer sesuai dengan paragraf 10 dari bagian yang sama

  1. ... Nilai konstanta karakter integer yang berisi lebih dari satu karakter (mis., 'Ab'), atau berisi karakter atau urutan escape yang tidak dipetakan ke karakter eksekusi byte tunggal, ditentukan oleh implementasi.

Jadi menurut kutipan yang disebutkan pertama itu bisa menjadi operan dari ekspresi konstan integer yang dapat digunakan sebagai label kasus.

Perhatikan bahwa konstanta karakter (diapit tanda kutip tunggal) memiliki tipe intdan tidak sama dengan string literal (urutan karakter yang diapit tanda kutip ganda) yang memiliki tipe larik karakter.

Vlad dari Moskow
sumber
12

Seperti yang dikatakan orang lain, ini adalah intkonstanta dan nilai aktualnya ditentukan oleh implementasi.

Saya berasumsi sisa kode terlihat seperti ini

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

Bisa dipastikan bahwa 'eax' di bagian pertama memiliki nilai yang sama dengan 'eax' di bagian kedua, jadi semuanya berhasil, bukan? ... salah.

Dalam komentar @Davislor mencantumkan beberapa kemungkinan nilai untuk 'eax':

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, atau sesuatu yang lain

Perhatikan nilai potensial pertama? Artinya 'e', mengabaikan dua karakter lainnya. Masalahnya adalah program mungkin menggunakan 'eax', 'ebx'dan sebagainya. Jika semua konstanta ini memiliki nilai yang sama dengan yang 'e'Anda hasilkan

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

Ini tidak terlihat terlalu bagus, bukan?

Bagian yang baik tentang "implementasi-didefinisikan" adalah bahwa pemrogram dapat memeriksa dokumentasi kompilator mereka dan melihat apakah ia melakukan sesuatu yang masuk akal dengan konstanta ini. Jika ya, rumah gratis.

Bagian buruknya adalah bahwa beberapa orang malang lainnya dapat mengambil kode dan mencoba mengkompilasinya menggunakan kompiler lain. Kesalahan kompilasi instan. Program ini tidak portabel.

Seperti yang ditunjukkan @zwol di komentar, situasinya tidak seburuk yang saya kira, dalam kasus buruk kode tidak dapat dikompilasi. Ini setidaknya akan memberi Anda nama file dan nomor baris yang tepat untuk masalah tersebut. Namun, Anda tidak akan memiliki program yang berfungsi.

Stig Hemmer
sumber
1
selain beberapa bentuk assert('eax' != 'ebx'); //if this fails you can't compile the code because...apakah ada sesuatu yang dapat dilakukan oleh penulis asli untuk mencegah kegagalan kompiler lain tanpa mengganti konstruksi seluruhnya>
Dan Is Fiddling By Firelight
6
Dua label case dengan nilai yang sama merupakan pelanggaran batasan (6.8.4.2p3: "... tidak ada dua case ekspresi konstanta dalam pernyataan switch yang sama akan memiliki nilai yang sama setelah konversi") jadi, selama semua kode memperlakukan nilai konstanta ini sebagai buram, ini dijamin akan berfungsi atau gagal untuk dikompilasi.
zwol
Bagian yang lebih buruk adalah bahwa orang yang malang yang mengkompilasi pada kompilator lain mungkin tidak akan melihat kesalahan waktu kompilasi (mengaktifkan int tidak masalah); sebaliknya, kesalahan waktu proses akan muncul ...
tucuxi
1

Fragmen kode menggunakan keanehan historis yang disebut konstanta karakter multi-karakter , juga disebut sebagai multi-karakter .

'eax' adalah konstanta integer yang nilainya ditentukan oleh implementasi.

Berikut adalah halaman yang menarik tentang multi-karakter dan bagaimana mereka dapat digunakan tetapi tidak seharusnya:

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


Melihat jauh ke belakang ke kaca spion, berikut adalah bagaimana manual C asli oleh Dennis Ritchie dari masa lalu yang baik ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) menentukan konstanta karakter .

2.3.2 Konstanta karakter

Konstanta karakter adalah 1 atau 2 karakter yang diapit tanda kutip tunggal '' '''. Dalam konstanta karakter, kutipan tunggal harus diawali dengan garis miring ke belakang '' \''. Karakter non-grafik tertentu, dan '' \'' itu sendiri, dapat di-escape sesuai dengan tabel berikut:

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

Escape '' \ddd'' terdiri dari garis miring terbalik diikuti dengan 1, 2, atau 3 digit oktal yang diambil untuk menentukan nilai karakter yang diinginkan. Kasus khusus dari konstruksi ini adalah '' \0'' (tidak diikuti dengan digit) yang menunjukkan karakter null.

Konstanta karakter berperilaku persis seperti bilangan bulat (bukan, secara khusus, seperti objek berjenis karakter). Sesuai dengan struktur pengalamatan PDP-11, konstanta karakter dengan panjang 1 memiliki kode untuk karakter yang diberikan dalam byte orde rendah dan 0 dalam byte orde tinggi; konstanta karakter dengan panjang 2 memiliki kode untuk karakter pertama dalam byte rendah dan untuk karakter kedua dalam byte orde tinggi. Konstanta karakter dengan lebih dari satu karakter secara inheren bergantung pada mesin dan harus dihindari.

Frasa terakhir adalah semua yang perlu Anda ingat tentang konstruksi aneh ini: Konstanta karakter dengan lebih dari satu karakter secara inheren bergantung pada mesin dan harus dihindari.

chqrlie.dll
sumber