Apakah operator logis hubungan pendek diamanatkan? Dan urutan evaluasi?

140

Apakah standar ANSI mengamanatkan operator logis untuk dihubung pendek, baik dalam C atau C ++?

Saya bingung karena saya ingat buku K&R yang mengatakan kode Anda tidak boleh bergantung pada operasi yang dihubung pendek ini, karena mungkin saja tidak. Bisakah seseorang tolong tunjukkan di mana dalam standar dikatakan ops logika selalu mengalami hubungan pendek? Saya sebagian besar tertarik pada C ++, jawaban juga untuk C akan lebih bagus.

Saya juga ingat membaca (tidak ingat di mana) bahwa urutan evaluasi tidak ditentukan secara ketat, jadi kode Anda tidak boleh bergantung atau menganggap fungsi dalam ekspresi akan dieksekusi dalam urutan tertentu: pada akhir pernyataan semua fungsi yang direferensikan akan dipanggil, tetapi kompiler memiliki kebebasan dalam memilih urutan yang paling efisien.

Apakah standar menunjukkan urutan evaluasi ungkapan ini?

if( functionA() && functionB() && functionC() ) cout<<"Hello world";
Joe Pineda
sumber
12
Carefull: Memang benar untuk tipe POD. Tetapi jika Anda membebani operator && atau operator || untuk kelas tertentu ini BUKAN saya ulangi BUKAN pintasan. Inilah mengapa disarankan agar Anda TIDAK mendefinisikan operator ini untuk kelas Anda sendiri.
Martin York
Saya mendefinisikan ulang operator ini beberapa waktu lalu, ketika saya membuat kelas yang akan melakukan beberapa operasi aljabar boolean dasar. Mungkin harus menempel komentar peringatan "ini menghancurkan hubungan arus pendek dan evaluasi kiri-kanan!" kalau-kalau saya lupa ini. Juga kelebihan * / + dan membuat mereka sinonim :-)
Joe Pineda
Memiliki panggilan fungsi dalam if-block bukanlah praktik pemrograman yang baik. Selalu ada variabel yang dideklarasikan yang menyimpan nilai balik metode dan menggunakannya dalam if-block.
SR Chaitanya
6
@ SRChaitanya Itu tidak benar. Apa yang Anda gambarkan sebagai praktik yang buruk dilakukan setiap saat, terutama dengan fungsi yang mengembalikan boolean, seperti di sini.
Marquis of Lorne

Jawaban:

154

Ya, hubungan arus pendek dan urutan evaluasi diperlukan untuk operator ||dan &&dalam standar C dan C ++.

Standar C ++ mengatakan (harus ada klausa yang setara dalam standar C):

1.9.18

Dalam evaluasi ekspresi berikut

a && b
a || b
a ? b : c
a , b

menggunakan makna bawaan operator dalam ekspresi ini, ada titik urutan setelah evaluasi ekspresi pertama (12).

Di C ++ ada jebakan tambahan: korsleting TIDAK berlaku untuk tipe yang membebani operator ||dan &&.

Catatan Kaki 12: Operator yang ditunjukkan dalam paragraf ini adalah operator bawaan, seperti yang dijelaskan dalam klausa 5. Ketika salah satu dari operator ini kelebihan beban (klausa 13) dalam konteks yang valid, dengan demikian menunjuk fungsi operator yang ditentukan pengguna, ekspresi menunjuk doa fungsi, dan operan membentuk daftar argumen, tanpa titik urutan tersirat di antara mereka.

Biasanya tidak disarankan untuk membebani operator ini dalam C ++ kecuali Anda memiliki persyaratan yang sangat spesifik. Anda dapat melakukannya, tetapi itu mungkin melanggar perilaku yang diharapkan dalam kode orang lain, terutama jika operator ini digunakan secara tidak langsung melalui instantiating templat dengan tipe overload operator ini.

Alex B
sumber
3
Tidak tahu hubungan arus pendek tidak akan berlaku untuk ops logika berlebih, itu menarik. Bisakah Anda menambahkan referensi ke standar, atau sumber? Saya tidak mempercayai Anda, hanya ingin belajar lebih banyak tentang ini.
Joe Pineda
4
ya, itu logis. itu bertindak sebagai argumen untuk operator && (a, b). implementasi itulah yang mengatakan apa yang terjadi.
Johannes Schaub - litb
10
litb: Tidak mungkin untuk meneruskan b ke operator && (a, b) tanpa mengevaluasinya. Dan tidak ada cara untuk mengevaluasi b karena kompiler tidak dapat menjamin tidak ada efek samping.
jmucchiello
2
Saya merasa sedih. Saya akan berpikir demikian, seandainya saya mendefinisikan ulang operator && dan || dan mereka masih sepenuhnya deterministik , kompiler akan mendeteksi itu dan membuat evaluasi mereka hubung singkat: setelah semua, pesanan tidak relevan, dan mereka menjamin tidak ada efek samping!
Joe Pineda
2
@ Jo: tapi nilai balik dan argumen operator dapat berubah dari boolean ke sesuatu yang lain. Saya dulu menerapkan logika "khusus" dengan nilai TIGA ("benar", "salah" dan "tidak dikenal"). Nilai pengembalian bersifat deterministik, tetapi perilaku hubungan arus pendek tidak tepat.
Alex B
70

Evaluasi hubung singkat, dan urutan evaluasi, adalah standar semantik yang diamanatkan dalam C dan C ++.

Jika tidak, kode seperti ini tidak akan menjadi ungkapan yang umum

   char* pChar = 0;
   // some actions which may or may not set pChar to something
   if ((pChar != 0) && (*pChar != '\0')) {
      // do something useful

   }

Bagian 6.5.13 Logical operator AND dari spesifikasi C99 (PDF link) mengatakan

(4) Tidak seperti biner & operator bitwise, operator && menjamin evaluasi kiri-ke-kanan; ada titik urutan setelah evaluasi operan pertama. Jika operan pertama sebanding dengan 0, operan kedua tidak dievaluasi.

Demikian pula, bagian 6.5.14 Logika ATAU operator kata

(4) Berbeda dengan bitwise | operator, || operator menjamin evaluasi kiri-ke-kanan; ada titik urutan setelah evaluasi operan pertama. Jika operan pertama membandingkan tidak sama dengan 0, operan kedua tidak dievaluasi.

Kata-kata yang mirip dapat ditemukan dalam standar C ++, periksa bagian 5.14 dalam draft draft ini . Seperti yang dicatat oleh catur dalam jawaban lain, jika Anda mengabaikan && atau ||, maka kedua operan harus dievaluasi karena menjadi panggilan fungsi biasa.

Paul Dixon
sumber
Ah, apa yang saya cari! OK, jadi urutan evaluasi dan hubungan pendek diamanatkan sesuai ANSI-C 99! Saya benar-benar ingin melihat referensi yang setara untuk ANSI-C ++, meskipun saya hampir 99% pasti sama.
Joe Pineda
Sulit menemukan tautan gratis yang bagus untuk standar C ++, telah ditautkan ke salinan konsep yang saya temukan dengan beberapa googling.
Paul Dixon
Benar untuk Jenis POD. Tetapi jika Anda membebani operator && atau operator || ini bukan jalan pintas.
Martin York
1
ya itu menarik untuk dicatat bahwa untuk bool, Anda akan selalu dijamin urutan evaluasi & perilaku hubung singkat. karena Anda tidak dapat membebani operator && untuk dua jenis bawaan. Anda memerlukan setidaknya satu tipe yang ditentukan pengguna dalam operan untuk membuatnya berperilaku berbeda.
Johannes Schaub - litb
Saya berharap saya bisa menerima catur dan jawaban ini. Karena saya sebagian besar tertarik pada C ++, saya menerima yang lain, meskipun harus mengakui ini hebat, juga! Terima kasih banyak!
Joe Pineda
19

Ya, itu mengamanatkan (urutan evaluasi dan hubungan pendek). Dalam contoh Anda jika semua fungsi mengembalikan true, urutan panggilan secara ketat dari functionA lalu functionB dan kemudian functionC. Digunakan untuk seperti ini

if(ptr && ptr->value) { 
    ...
}

Sama untuk operator koma:

// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b()); 

Salah satu kata antara kiri dan operan kanan &&, ||, ,dan antara pertama dan kedua / operan ketiga ?:(operator kondisional) adalah "titik urutan". Setiap efek samping dievaluasi sepenuhnya sebelum titik itu. Jadi, ini aman:

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1

Perhatikan bahwa operator koma tidak perlu bingung dengan koma sintaksis yang digunakan untuk memisahkan hal-hal:

// order of calls to a and b is unspecified!
function(a(), b());

Standar C ++ mengatakan 5.14/1:

Grup && operator kiri-ke-kanan. Operan keduanya secara implisit dikonversi menjadi tipe bool (klausa 4). Hasilnya benar jika kedua operan benar dan salah jika tidak. Tidak seperti &, && menjamin evaluasi dari kiri ke kanan: operan kedua tidak dievaluasi jika operan pertama salah.

Dan di 5.15/1:

The || grup operator kiri-ke-kanan. Operan keduanya secara implisit dikonversi menjadi bool (klausa 4). Ini mengembalikan true jika salah satu operan-nya benar, dan salah sebaliknya. Berbeda dengan |, || menjamin evaluasi kiri-ke-kanan; selain itu, operan kedua tidak dievaluasi jika operan pertama bernilai true.

Dikatakan untuk keduanya di sebelah:

Hasilnya adalah bool. Semua efek samping dari ekspresi pertama kecuali untuk penghancuran sementara (12.2) terjadi sebelum ekspresi kedua dievaluasi.

Selain itu, 1.9/18kata

Dalam evaluasi masing-masing ungkapan

  • a && b
  • a || b
  • a ? b : C
  • a , b

menggunakan makna bawaan dari operator dalam ekspresi ini (5.14, 5.15, 5.16, 5.18), ada titik urutan setelah evaluasi ekspresi pertama.

Johannes Schaub - litb
sumber
10

Langsung dari K&R tua yang baik:

C menjamin bahwa &&dan ||dievaluasi dari kiri ke kanan - kami akan segera melihat kasus-kasus di mana hal ini penting.

John T
sumber
3
K&R edisi ke-2 hlm. "Ekspresi yang dihubungkan oleh && atau || dievaluasi dari kiri ke kanan, dan evaluasi berhenti segera setelah kebenaran atau kepalsuan hasil diketahui. Sebagian besar program C mengandalkan properti ini." Saya tidak dapat menemukan teks yang Anda kutip di mana pun di buku ini. Apakah ini dari edisi pertama yang sangat usang? Harap jelaskan di mana Anda menemukan teks ini.
Lundin
1
Ok ternyata Anda mengutip tutorial kuno ini . Itu dari 1974 dan sangat tidak relevan.
Lundin
6

Berhati-hatilah.

Untuk tipe fundamental, ini adalah operator pintasan.

Tetapi jika Anda mendefinisikan operator ini untuk kelas Anda sendiri atau tipe enumerasi mereka bukan jalan pintas. Karena perbedaan semantik ini dalam penggunaannya dalam keadaan yang berbeda ini, disarankan agar Anda tidak mendefinisikan operator ini.

Untuk operator &&dan operator ||untuk tipe dasar urutan evaluasi dibiarkan dari kanan (jika tidak, pemotongan pendek akan sulit :-) Tetapi untuk operator kelebihan beban yang Anda tetapkan, ini pada dasarnya adalah sintaksis gula untuk mendefinisikan metode dan dengan demikian urutan evaluasi parameter adalah tidak terdefinisi.

Martin York
sumber
1
Overloading operator tidak ada hubungannya dengan tipe POD atau tidak. Untuk mendefinisikan fungsi operator, setidaknya salah satu argumen harus berupa kelas (atau struct atau gabungan) atau enum, atau referensi ke salah satu dari itu. Menjadi POD berarti Anda dapat menggunakan memcpy di atasnya.
Derek Ledbetter
Dan itulah yang saya katakan. Jika Anda kelebihan && untuk kelas Anda maka itu benar-benar hanya panggilan metode. Dengan demikian Anda tidak dapat mengandalkan urutan evaluasi dari parameter. Jelas Anda tidak bisa kelebihan && untuk tipe POD.
Martin York
3
Anda menggunakan istilah "tipe POD" secara tidak benar. Anda dapat kelebihan && untuk struct, kelas, gabungan atau enum, POD atau tidak. Anda tidak dapat kelebihan && jika kedua sisi adalah tipe angka atau petunjuk.
Derek Ledbetter
Saya menggunakan POD sebagai (char / int / float dll) bukan POD yang memperburuk (yang Anda bicarakan) dan biasanya disebut secara terpisah atau lebih eksplisit karena itu bukan tipe bawaan.
Martin York
2
Jadi maksudmu "tipe fundamental" tetapi menulis "tipe POD"?
Öö Tiib
0

Pertanyaan Anda sampai pada prioritas operator C ++ dan asosiasi. Pada dasarnya, dalam ekspresi dengan beberapa operator dan tanpa tanda kurung, kompiler membangun pohon ekspresi dengan mengikuti aturan-aturan ini.

Untuk didahulukan, ketika Anda memiliki sesuatu seperti A op1 B op2 C, Anda dapat mengelompokkan hal-hal sebagai (A op1 B) op2 Catau A op1 (B op2 C). Jika op1memiliki prioritas lebih tinggi daripada op2, Anda akan mendapatkan ekspresi pertama. Kalau tidak, Anda akan mendapatkan yang kedua.

Untuk asosiatif, ketika Anda memiliki sesuatu seperti A op B op C, Anda dapat mengelompokkan kembali sebagai (A op B) op Catau A op (B op C). Jika optelah meninggalkan asosiasi, kita berakhir dengan ekspresi pertama. Jika asosiasi itu benar, kita berakhir dengan asosiasi kedua. Ini juga berfungsi untuk operator pada tingkat prioritas yang sama.

Dalam kasus khusus ini, &&memiliki prioritas lebih tinggi daripada ||, sehingga ekspresi akan dievaluasi sebagai (a != "" && it == seqMap.end()) || isEven.

Urutan itu sendiri adalah "kiri-ke-kanan" pada bentuk pohon ekspresi. Jadi pertama-tama kita akan mengevaluasi a != "" && it == seqMap.end(). Jika itu benar seluruh ekspresi itu benar, kalau tidak kita pergi ke isEven. Prosedur ini berulang dengan sendirinya di dalam sub-ekspresi kiri saja.


Berita menarik, tetapi konsep diutamakan berakar pada notasi matematika. Hal yang sama terjadi di a*b + c, di mana *memiliki prioritas lebih tinggi daripada +.

Bahkan lebih menarik / tidak jelas, untuk ekspresi yang tidak dipalsukan A1 op1 A2 op2 ... opn-1 An, di mana semua operator memiliki prioritas yang sama, jumlah pohon ekspresi biner yang dapat kita bentuk diberikan oleh apa yang disebut angka Catalan . Untuk yang besar n, ini tumbuh sangat cepat. d

Horia Coman
sumber
Semua ini benar, tetapi ini tentang prioritas operator dan asosiasi, bukan tentang urutan evaluasi dan curcuiting pendek. Itu adalah hal yang berbeda.
Thomas Padron-McCarthy
0

Jika Anda mempercayai Wikipedia:

[ &&dan ||] secara semantik berbeda dari operator bit-wise & dan | karena mereka tidak akan pernah mengevaluasi operan yang tepat jika hasilnya dapat ditentukan dari kiri saja

C (bahasa pemrograman)

Sophie Alpert
sumber
11
Mengapa mempercayai wiki ketika kami memiliki standar!
Martin York
1
Jika Anda mempercayai Wikipedia, 'Wikipedia bukanlah sumber yang dapat diandalkan' .
Marquis of Lorne
Sejauh ini memang benar, tetapi tidak lengkap, karena operator yang kelebihan beban di C ++ tidak mengalami hubungan pendek.
Thomas Padron-McCarthy