Mengapa operator TIDAK logis dalam bahasa C-style "!" Dan bukan "~~"?

40

Untuk operator biner, kami memiliki operator bitwise dan logical:

& bitwise AND
| bitwise OR

&& logical AND
|| logical OR

BUKAN (operator unary) berperilaku berbeda. Ada ~ untuk bitwise dan! untuk logika.

Saya mengenali NOT adalah operasi yang tidak disadari sebagai lawan dari AND dan ATAU tetapi saya tidak dapat memikirkan alasan mengapa para perancang memilih untuk menyimpang dari prinsip bahwa single bitwise dan double logis di sini, dan sebagai gantinya memilih karakter yang berbeda. Saya kira Anda bisa membacanya salah, seperti operasi bitwise ganda yang akan selalu mengembalikan nilai operan. Tapi itu sepertinya bukan masalah bagi saya.

Apakah ada alasan saya hilang?

Martin Maat
sumber
7
Karena jika !! berarti tidak logis, bagaimana saya mengubah 42 menjadi 1? :)
candied_orange
9
Akan ~~kemudian tidak lebih konsisten untuk logika NOT, jika Anda mengikuti pola yang operator logis adalah dua kali lipat dari operator bitwise?
Bart van Ingen Schenau
9
Pertama, jika itu untuk konsistensi itu akan ~ dan ~~ Penggandaan dan dan atau terkait dengan hubungan pendek; dan yang logis tidak tidak memiliki hubungan pendek.
Christophe
3
Saya menduga alasan desain yang mendasarinya adalah kejelasan visual dan perbedaan, dalam kasus penggunaan yang khas. Operator biner (yaitu, dua operan) adalah infiks (dan cenderung dipisahkan oleh spasi), sedangkan operator unary adalah awalan (dan cenderung tidak diberi spasi).
Steve
7
Karena beberapa komentar telah menyinggung (dan bagi mereka yang tidak ingin mengikuti tautan ini , !!fooadalah idiom yang tidak biasa (tidak tidak umum?). Ini menormalkan argumen nol atau bukan nol untuk 0atau 1.
Keith Thompson

Jawaban:

110

Anehnya, sejarah bahasa pemrograman C-style tidak dimulai dengan C.

Dennis Ritchie menjelaskan dengan baik tantangan kelahiran C dalam artikel ini .

Ketika membacanya, menjadi jelas bahwa C mewarisi bagian dari desain bahasanya dari BCPL pendahulunya , dan terutama operator. Bagian “Neonatal C” dari artikel tersebut menjelaskan bagaimana BCPL &dan |diperkaya dengan dua operator baru &&dan ||. Alasannya adalah:

  • prioritas berbeda diperlukan karena penggunaannya dalam kombinasi dengan ==
  • evaluasi yang berbeda logika: kiri ke kanan evaluasi dengan hubungan arus pendek (yaitu ketika aadalah falsedi a&&b, btidak dievaluasi).

Menariknya, penggandaan ini tidak menciptakan ambiguitas bagi pembaca: a && btidak akan disalahartikan sebagai a(&(&b)). Dari sudut pandang parsing, tidak ada ambiguitas baik: &bbisa masuk akal jika bmerupakan nilai, tetapi itu akan menjadi pointer sedangkan bitwise &akan membutuhkan operan integer, sehingga logika DAN akan menjadi satu-satunya pilihan yang masuk akal.

BCPL sudah digunakan ~untuk negasi bitwise. Jadi dari sudut pandang konsistensi, bisa digandakan untuk memberikan ~~arti logis. Sayangnya ini akan menjadi sangat ambigu karena ~merupakan operator unary: ~~bbisa juga berarti ~(~b)). Inilah sebabnya mengapa simbol lain harus dipilih untuk negasi yang hilang.

Christophe
sumber
10
Pengurai tidak dapat membingungkan dua situasi, oleh karena itu perancang bahasa harus melakukannya.
BobDalgleish
16
@Steve: Memang, ada banyak masalah serupa sudah dalam bahasa C dan C-like. Ketika parser melihat (t)+1apakah itu merupakan tambahan (t)dan 1atau apakah itu pemeran +1untuk mengetik t? Desain C ++ harus menyelesaikan masalah bagaimana lex template yang berisi >>dengan benar. Dan seterusnya.
Eric Lippert
6
@ user2357112 Saya pikir intinya adalah bahwa boleh-boleh saja mengambil tokenizer secara membabi buta &&sebagai &&token tunggal dan bukan sebagai dua &token, karena a & (&b)penafsirannya bukan hal yang masuk akal untuk ditulis, sehingga manusia tidak akan pernah bermaksud dan terkejut oleh kompiler memperlakukannya sebagai a && b. Sedangkan keduanya !(!a)dan !!amerupakan hal-hal yang mungkin bagi manusia untuk diartikan, jadi itu adalah ide yang buruk bagi kompiler untuk menyelesaikan ambiguitas dengan aturan level tokenization yang sewenang-wenang.
Ben
18
!!tidak hanya mungkin / masuk akal untuk menulis, tetapi idiom kanonik "convert to boolean".
R ..
4
Saya pikir dan04 mengacu pada ambiguitas --avs -(-a), keduanya valid secara sintaksis tetapi memiliki semantik yang berbeda.
Ruslan
49

Saya tidak dapat memikirkan alasan mengapa para desainer memilih untuk menyimpang dari prinsip bahwa tunggal itu bitwise dan double adalah logis di sini,

Itu bukan prinsip di tempat pertama; begitu Anda menyadarinya, itu lebih masuk akal.

Cara yang lebih baik untuk memikirkan &vs &&bukan biner dan Boolean . Cara yang lebih baik adalah menganggap mereka bersemangat dan malas . The &Operator mengeksekusi sisi kiri dan kanan dan kemudian menghitung hasilnya. The &&Operator mengeksekusi sisi kiri, dan kemudian mengeksekusi sisi kanan hanya jika diperlukan untuk menghitung hasilnya.

Selain itu, alih-alih berpikir tentang "biner" dan "Boolean", pikirkan tentang apa yang sebenarnya terjadi. Versi "biner" hanya melakukan operasi Boolean pada array Boolean yang telah dikemas menjadi sebuah kata .

Jadi mari kita kumpulkan. Apakah masuk akal untuk melakukan operasi malas pada array Boolean ? Tidak, karena tidak ada "sisi kiri" untuk diperiksa terlebih dahulu. Ada 32 "sisi kiri" untuk diperiksa terlebih dahulu. Jadi kami membatasi operasi malas ke satu Boolean, dan dari situlah intuisi Anda bahwa salah satunya adalah "biner" dan satu adalah "Boolean" berasal, tetapi itu adalah konsekuensi dari desain, bukan desain itu sendiri!

Dan ketika Anda memikirkannya seperti itu, menjadi jelas mengapa tidak ada !!dan tidak ^^. Tak satu pun dari operator tersebut memiliki properti yang dapat Anda lewati saat menganalisis salah satu operan; tidak ada "malas" notatau xor.

Bahasa lain membuatnya lebih jelas; beberapa bahasa digunakan anduntuk berarti "bersemangat dan" tetapi and alsoberarti "malas dan", misalnya. Dan bahasa lain juga membuatnya lebih jelas &dan &&bukan "biner" dan "Boolean"; misalnya dalam C #, kedua versi dapat menggunakan Boolean sebagai operan.

Eric Lippert
sumber
2
Terima kasih. Ini adalah pembuka mata nyata bagi saya. Sayang sekali saya tidak bisa menerima dua jawaban.
Martin Maat
11
Saya tidak berpikir ini adalah cara yang baik untuk memikirkan &dan &&. Sementara eagerness adalah salah satu perbedaan antara &dan &&, &berperilaku sangat berbeda dari versi yang bersemangat &&, terutama dalam bahasa di mana &&mendukung jenis selain jenis boolean khusus.
user2357112 mendukung Monica
14
Misalnya, dalam C dan C ++, 1 & 2memiliki hasil yang sama sekali berbeda dari 1 && 2.
user2357112 mendukung Monica
7
@ZizyArcher: Seperti yang saya catat di komentar di atas, keputusan untuk menghapus suatu booltipe C memiliki efek knock-on. Kita membutuhkan keduanya !dan ~karena satu berarti "memperlakukan int sebagai Boolean tunggal" dan satu berarti "memperlakukan int sebagai array Boolean yang dikemas". Jika Anda memiliki tipe bool dan int yang terpisah maka Anda dapat memiliki hanya satu operator, yang menurut saya akan menjadi desain yang lebih baik, tapi kami hampir terlambat 50 tahun pada operator itu. C # mempertahankan desain ini agar tidak asing.
Eric Lippert
3
@ Steve: Jika jawabannya tampak tidak masuk akal maka saya telah membuat argumen yang diekspresikan dengan buruk di suatu tempat, dan kita seharusnya tidak mengandalkan argumen dari otoritas. Bisakah Anda mengatakan lebih banyak tentang apa yang tampaknya tidak masuk akal tentang hal itu?
Eric Lippert
21

TL; DR

C mewarisi !dan ~operator dari bahasa lain. Keduanya &&dan ||ditambahkan bertahun-tahun kemudian oleh orang yang berbeda.

Jawaban panjang

Secara historis, C dikembangkan dari bahasa awal B, yang didasarkan pada BCPL, yang didasarkan pada CPL, yang didasarkan pada Algol.

Algol , kakek buyut C ++, Java dan C #, mendefinisikan benar dan salah dengan cara yang terasa intuitif bagi para programmer: "nilai kebenaran yang, dianggap sebagai angka biner (benar sesuai dengan 1 dan salah ke 0), adalah sama dengan nilai integral intrinsik ”. Namun, satu kelemahan dari ini adalah bahwa logis dan bitwise tidak tidak bisa menjadi operasi yang sama: Pada komputer modern apa pun, ~0sama dengan -1 daripada 1 dan ~1sama dengan -2 daripada 0. (Bahkan pada beberapa mainframe berusia enam puluh tahun di mana ~0mewakili - 0 atau INT_MIN, ~0 != 1pada setiap CPU yang pernah dibuat, dan standar bahasa C telah mengharuskannya selama bertahun-tahun, sementara sebagian besar bahasa putrinya bahkan tidak repot-repot mendukung tanda-dan-besarnya atau komplemen seseorang sama sekali.)

Algol mengatasi ini dengan memiliki mode berbeda dan menafsirkan operator berbeda dalam mode boolean dan integral. Yaitu, operasi bitwise adalah pada tipe integer, dan operasi logis adalah pada tipe boolean.

BCPL memiliki tipe boolean yang terpisah, tetapi satu notoperator , untuk bitwise dan logical tidak. Cara cikal bakal awal C ini bekerja adalah:

Nilai dari true adalah pola bit yang seluruhnya terdiri dari yang; nilai salah adalah nol.

Catat itu true = ~ false

(Anda akan mengamati bahwa istilah nilai p telah berkembang berarti sesuatu yang sama sekali berbeda dalam bahasa C-keluarga. Kami akan hari ini panggilan itu “objek representasi” di C.)

Definisi ini akan memungkinkan logis dan bitwise untuk tidak menggunakan instruksi bahasa mesin yang sama. Jika C telah menempuh rute itu, file header di seluruh dunia akan mengatakan #define TRUE -1.

Tetapi bahasa pemrograman B diketik dengan lemah, dan tidak memiliki tipe boolean atau bahkan floating-point. Semuanya setara intdengan penggantinya, C. Ini membuatnya menjadi ide yang baik bagi bahasa untuk mendefinisikan apa yang terjadi ketika suatu program menggunakan nilai selain benar atau salah sebagai nilai logis. Pertama-tama didefinisikan ekspresi yang benar sebagai "tidak sama dengan nol." Ini efisien pada minicomputer tempat ia berlari, yang memiliki flag nol CPU.

Ada, pada saat itu, sebuah alternatif: CPU yang sama juga memiliki flag negatif, dan nilai kebenaran BCPL adalah -1, jadi B mungkin telah mendefinisikan semua angka negatif sebagai benar dan semua angka non-negatif sebagai kepalsuan. (Ada satu sisa dari pendekatan ini: UNIX, yang dikembangkan oleh orang yang sama pada saat yang sama, mendefinisikan semua kode kesalahan sebagai bilangan bulat negatif. Banyak panggilan sistemnya mengembalikan salah satu dari beberapa nilai negatif kegagalan yang berbeda.) Jadi bersyukurlah: bisa lebih buruk!

Tetapi mendefinisikan TRUEsebagai 1dan FALSEseperti 0dalam B berarti bahwa identitas true = ~ falsetidak lagi dipegang, dan itu telah menghilangkan ketikan yang kuat yang memungkinkan Algol untuk ambigu antara ekspresi bitwise dan logis. Untuk itu diperlukan operator logis-bukan yang baru, dan desainer memilih !, mungkin karena sudah tidak sama dengan yang sudah !=, yang terlihat seperti bar vertikal melalui tanda sama. Mereka tidak mengikuti konvensi yang sama dengan &&atau ||karena belum ada satu pun.

Boleh dibilang, mereka harus memiliki: &operator di B rusak seperti yang dirancang. Di B dan di C, 1 & 2 == FALSEmeskipun 1dan 2keduanya adalah nilai-nilai yang benar, dan tidak ada cara intuitif untuk mengekspresikan operasi logis dalam B. Itu adalah satu kesalahan C mencoba untuk memperbaiki sebagian dengan menambahkan &&dan ||, tetapi perhatian utama pada saat itu adalah untuk akhirnya mendapatkan hubungan arus pendek, dan membuat program berjalan lebih cepat. Buktinya adalah bahwa tidak ada ^^: 1 ^ 2adalah nilai yang benar meskipun kedua operannya benar, tetapi tidak dapat mengambil manfaat dari hubungan arus pendek.

Davislor
sumber
4
+1. Saya pikir ini adalah tur berpemandu yang cukup baik di sekitar evolusi operator ini.
Steve
BTW, tanda / magnitudo dan mesin pelengkap seseorang juga perlu bitwise vs negasi logis terpisah, bahkan jika input sudah booleanized. ~0(semua bit diatur) adalah nol komplemen negatif seseorang (atau representasi perangkap). Tanda / magnitudo ~0adalah angka negatif dengan magnitudo maksimum.
Peter Cordes
@PeterCordes Anda memang benar. Saya hanya fokus pada mesin dua pelengkap karena mereka jauh lebih penting. Mungkin itu layak dicatat.
Davislor
Saya pikir komentar saya sudah cukup, tapi ya, mungkin tanda kurung (tidak berfungsi untuk komplemen 1 atau tanda / besarnya juga) akan menjadi suntingan yang bagus.
Peter Cordes