Titik perilaku dan urutan yang tidak ditentukan

987

Apa itu "titik urut"?

Apa hubungan antara perilaku yang tidak ditentukan dan titik urut?

Saya sering menggunakan ekspresi lucu dan berbelit-belit seperti a[++i] = i;, untuk membuat diri saya merasa lebih baik. Mengapa saya harus berhenti menggunakannya?

Jika Anda sudah membaca ini, pastikan untuk mengunjungi pertanyaan tindak lanjut Perilaku tidak terdefinisi dan poin urutan dimuat ulang .

(Catatan: Ini dimaksudkan sebagai entri untuk FAQ C ++ Stack Overflow . Jika Anda ingin mengkritik gagasan memberikan FAQ dalam formulir ini, maka posting pada meta yang memulai semua ini akan menjadi tempat untuk melakukan itu. Jawaban untuk pertanyaan itu dipantau di chatroom C ++ , di mana ide FAQ dimulai sejak awal, jadi jawaban Anda sangat mungkin untuk dibaca oleh mereka yang mengemukakan ide itu.)

tidak diketahui
sumber

Jawaban:

683

C ++ 98 dan C ++ 03

Jawaban ini untuk versi standar C ++ yang lebih lama. Versi C ++ 11 dan C ++ 14 standar tidak secara formal mengandung 'poin urutan'; operasi 'diurutkan sebelum' atau 'tidak diurutkan' atau 'diurutkan tak tentu'. Efek bersihnya pada dasarnya sama, tetapi terminologinya berbeda.


Penafian : Oke. Jawaban ini agak panjang. Jadi bersabarlah saat membacanya. Jika Anda sudah tahu hal-hal ini, membacanya lagi tidak akan membuat Anda gila.

Prasyarat : Pengetahuan dasar tentang C ++ Standard


Apa itu Poin Urutan?

Standar mengatakan

Pada titik tertentu tertentu dalam urutan eksekusi yang disebut titik sekuens , semua efek samping dari evaluasi sebelumnya harus lengkap dan tidak ada efek samping dari evaluasi selanjutnya yang akan terjadi. (§1.9 / 7)

Efek samping? Apa efek sampingnya?

Evaluasi suatu ekspresi menghasilkan sesuatu dan jika selain itu ada perubahan dalam kondisi lingkungan eksekusi dikatakan bahwa ekspresi (evaluasinya) memiliki beberapa efek samping.

Sebagai contoh:

int x = y++; //where y is also an int

Selain operasi inisialisasi, nilai yakan berubah karena efek samping dari ++operator.

Sejauh ini bagus. Pindah ke titik urutan. Definisi pergantian poin seq yang diberikan oleh penulis comp.lang.c Steve Summit:

Titik sekuens adalah titik di mana debu telah mengendap dan semua efek samping yang telah terlihat sejauh ini dijamin akan lengkap.


Apa titik urutan umum yang tercantum dalam Standar C ++?

Yaitu:

  • pada akhir evaluasi ekspresi penuh ( §1.9/16) (Ekspresi penuh adalah ekspresi yang bukan merupakan subekspresi dari ekspresi lain.) 1

    Contoh:

    int a = 5; // ; is a sequence point here
  • dalam evaluasi masing-masing ekspresi berikut setelah evaluasi ekspresi pertama ( §1.9/18) 2

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18)(di sini a, b adalah operator koma; di func(a,a++) ,bukan operator koma, itu hanya pemisah antara argumen adan a++. Dengan demikian perilaku tidak terdefinisi dalam kasus itu (jika adianggap sebagai tipe primitif))
  • pada panggilan fungsi (apakah fungsi inline atau tidak), setelah evaluasi semua argumen fungsi (jika ada) yang terjadi sebelum eksekusi ekspresi atau pernyataan apa pun di badan fungsi ( §1.9/17).

1: Catatan: evaluasi ekspresi penuh dapat mencakup evaluasi subekspresi yang bukan bagian leksikal dari ekspresi penuh. Misalnya, subekspresi yang terlibat dalam mengevaluasi ekspresi argumen default (8.3.6) dianggap dibuat dalam ekspresi yang memanggil fungsi, bukan ekspresi yang mendefinisikan argumen default

2: Operator yang ditunjukkan adalah operator built-in, 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 pada pemanggilan fungsi dan operan membentuk daftar argumen, tanpa titik urutan tersirat di antara mereka.


Apa itu Perilaku Tidak Terdefinisi?

Standar ini mendefinisikan Perilaku Tidak Terdefinisi dalam Bagian §1.3.12sebagai

perilaku, seperti yang mungkin timbul pada saat penggunaan konstruksi program yang keliru atau data yang salah, yang untuknya Standar Internasional ini tidak menetapkan persyaratan 3 .

Perilaku tidak terdefinisi juga dapat diharapkan ketika Standar Internasional ini menghilangkan deskripsi dari setiap definisi perilaku yang eksplisit.

3: perilaku yang tidak terdefinisi yang diperbolehkan berkisar dari mengabaikan situasi sepenuhnya dengan hasil yang tidak terduga, hingga berperilaku selama penerjemahan atau pelaksanaan program dengan cara yang terdokumentasi dengan karakteristik lingkungan (dengan atau tanpa penerbitan pesan diagnostik), hingga penghentian terjemahan atau eksekusi (dengan penerbitan pesan diagnostik).

Singkatnya, perilaku yang tidak terdefinisi berarti segala sesuatu dapat terjadi mulai dari daemon yang terbang keluar dari hidung Anda hingga pacar Anda hamil.


Apa hubungan antara Perilaku Tidak Ditentukan dan Poin Urutan?

Sebelum saya membahasnya, Anda harus mengetahui perbedaan antara Perilaku Tidak Terdefinisi, Perilaku Tidak Ditentukan, dan Perilaku yang Didefinisikan Implementasi .

Anda juga harus tahu itu the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Sebagai contoh:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

Contoh lain di sini .


Sekarang Standar dalam §5/4kata

  • 1) Antara titik urutan sebelumnya dan berikutnya objek skalar harus memiliki nilai tersimpan dimodifikasi paling banyak sekali dengan evaluasi suatu ekspresi.

Apa artinya?

Secara tidak resmi itu berarti bahwa antara dua titik urutan suatu variabel tidak boleh dimodifikasi lebih dari sekali. Dalam pernyataan ekspresi, next sequence pointbiasanya di titik koma terminasi, dan previous sequence pointdi akhir pernyataan sebelumnya. Ekspresi juga dapat mengandung perantara sequence points.

Dari kalimat di atas, ekspresi berikut memunculkan Perilaku Tidak Terdefinisi:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

Tapi ekspresi berikut baik-baik saja:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

  • 2) Selanjutnya, nilai sebelumnya harus diakses hanya untuk menentukan nilai yang akan disimpan.

Apa artinya? Ini berarti jika suatu objek ditulis ke dalam ekspresi penuh, setiap dan semua akses ke sana dalam ekspresi yang sama harus secara langsung terlibat dalam perhitungan nilai yang akan ditulis .

Misalnya dalam i = i + 1semua akses i(dalam LHS dan dalam RHS) secara langsung terlibat dalam perhitungan nilai yang akan ditulis. Jadi tidak apa-apa.

Aturan ini secara efektif membatasi ekspresi hukum pada akses yang secara nyata mendahului modifikasi.

Contoh 1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Contoh 2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

tidak diizinkan karena salah satu akses i(yang masuk a[i]) tidak ada hubungannya dengan nilai yang akhirnya disimpan di i (yang terjadi berulang i++), dan jadi tidak ada cara yang baik untuk mendefinisikan - baik untuk pemahaman kita atau compiler's - apakah akses harus dilakukan sebelum atau setelah nilai yang bertambah disimpan. Jadi perilakunya tidak terdefinisi.

Contoh 3:

int x = i + i++ ;// Similar to above

Tindak lanjut jawaban untuk C ++ 11 di sini .

Prasoon Saurav
sumber
45
*p++ = 4 bukan Perilaku Tidak Terdefinisi. *p++diartikan sebagai *(p++). p++mengembalikan p(salinan) dan nilai yang tersimpan di alamat sebelumnya. Mengapa itu memohon pada UB? Tidak apa-apa.
Prasoon Saurav
7
@ Mike: AFAIK, tidak ada salinan (legal) dari C ++ Standard yang bisa Anda tautkan.
sbi
11
Nah, maka Anda bisa memiliki tautan ke halaman pemesanan ISO yang relevan. Ngomong-ngomong, memikirkannya, frasa "pengetahuan dasar C ++ Standar" tampaknya sedikit kontradiksi dalam istilah, karena jika Anda membaca standar, Anda melewati tingkat dasar. Mungkin kita bisa membuat daftar hal-hal apa dalam bahasa yang Anda butuhkan pemahaman dasar, seperti sintaksis ekspresi, urutan operasi, dan mungkin kelebihan operator?
Mike DeSimone
41
Saya tidak yakin mengutip standar adalah cara terbaik untuk mengajar pemula
Inverse
6
@Adrian Ekspresi pertama memanggil UB karena tidak ada titik urutan antara yang terakhir ++idan yang ditugaskan i. Ekspresi kedua tidak memanggil UB karena ekspresi itidak mengubah nilai i. Dalam contoh kedua i++diikuti oleh titik urutan ( ,) sebelum operator penugasan dipanggil.
Kolyunya
276

Ini adalah tindak lanjut dari jawaban saya sebelumnya dan berisi materi yang terkait dengan C ++ 11. .


Prasyarat : Pengetahuan dasar tentang Hubungan (Matematika).


Benarkah tidak ada Sequence Points di C ++ 11?

Iya! Ini sangat benar.

Sequence Points telah digantikan oleh Sequencing Before dan Sequenced After (dan Unafterenced dan Indeterminately Sequencing ) hubungan di C ++ 11.


Apa sebenarnya hal 'Diurutkan sebelum' ini?

Sequencing Before (§1.9 / 13) adalah relasi yang:

antara evaluasi yang dilakukan oleh utas tunggal dan menginduksi urutan parsial yang ketat 1

Secara formal itu berarti diberi dua evaluasi (Lihat di bawah) A dan B, jika Asudah diurutkan sebelum B , maka eksekusi A akan mendahului pelaksanaan B. Jika Atidak diurutkan sebelum Bdan Btidak diurutkan sebelumnya A, maka Adan Btidak dilanjutkan 2 .

Mengevaluasi Adan Bsecara tidak pasti diurutkan ketika salah Adiurutkan sebelum Batau Bdiurutkan sebelumnya A, tetapi tidak ditentukan yang mana 3 .

[CATATAN]
1: Sebuah urutan parsial yang ketat adalah relasi biner "<" lebih satu set Pyang asymmetric, dan transitive, yaitu, untuk semua a, bdan cdi P, kita mendapati bahwa:
........ (i). jika a <b maka ¬ (b <a) ( asymmetry);
........ (ii). jika a <b dan b <c maka a <c ( transitivity).
2: Pelaksanaan evaluasi yang tidak dilakukan dapat tumpang tindih .
3: evaluasi yang diurutkan secara tidak ditentukan tidak dapat tumpang tindih , tetapi keduanya dapat dieksekusi terlebih dahulu.


Apa arti dari kata 'evaluasi' dalam konteks C ++ 11?

Dalam C ++ 11, evaluasi ekspresi (atau sub-ekspresi) secara umum meliputi:

  • perhitungan nilai (termasuk menentukan identitas suatu objek untuk evaluasi glvalue dan mengambil nilai yang sebelumnya ditugaskan ke objek untuk evaluasi prvalue ) dan

  • inisiasi efek samping .

Sekarang (§1.9 / 14) mengatakan:

Setiap perhitungan nilai dan efek samping yang terkait dengan ekspresi penuh diurutkan sebelum setiap perhitungan nilai dan efek samping yang terkait dengan ekspresi penuh berikutnya yang akan dievaluasi .

  • Contoh sepele:

    int x; x = 10; ++x;

    Perhitungan nilai dan efek samping yang terkait dengan ++xdiurutkan setelah perhitungan nilai dan efek samping darix = 10;


Jadi harus ada hubungan antara Perilaku Tidak Terdefinisi dan hal-hal yang disebutkan di atas, kan?

Iya! Baik.

Dalam (§1.9 / 15) telah disebutkan itu

Kecuali jika disebutkan, evaluasi operan dari operator individual dan subekspresi dari ekspresi individu tidak dilakukan 4 .

Sebagai contoh :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Evaluasi operan +operator tidak diprioritaskan satu sama lain.
  2. Evaluasi operan <<dan >>operator tidak dilakukan relatif terhadap satu sama lain.

4: Dalam ekspresi yang dievaluasi lebih dari satu kali selama pelaksanaan suatu program, evaluasi subekspresi yang tidak ditentukan dan tidak ditentukan secara berurutan tidak perlu dilakukan secara konsisten dalam evaluasi yang berbeda.

(§1.9 / 15) Perhitungan nilai operan operator diurutkan sebelum perhitungan nilai hasil operator.

Itu berarti dalam x + yperhitungan nilai xdan ydiurutkan sebelum perhitungan nilai (x + y).

Lebih penting

(§1.9 / 15) Jika efek samping pada objek skalar tidak dipengaruhi relatif terhadap keduanya

(a) efek samping lain pada objek skalar yang sama

atau

(B) perhitungan nilai menggunakan nilai objek skalar yang sama.

perilaku tidak terdefinisi .

Contoh:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

Ketika memanggil suatu fungsi (apakah fungsi tersebut sebaris atau tidak), setiap perhitungan nilai dan efek samping yang terkait dengan ekspresi argumen apa pun, atau dengan ekspresi postfix yang menunjuk fungsi yang dipanggil, diurutkan sebelum eksekusi setiap ekspresi atau pernyataan dalam tubuh disebut fungsi. [ Catatan: Perhitungan nilai dan efek samping yang terkait dengan ekspresi argumen berbeda tidak dijelaskan sebelumnya . - catatan akhir ]

Ekspresi (5), (7)dan (8)jangan aktifkan perilaku yang tidak terdefinisi. Lihatlah jawaban berikut untuk penjelasan yang lebih rinci.


Catatan Akhir :

Jika Anda menemukan kesalahan dalam pos silakan tinggalkan komentar. Pengguna yang kuat (Dengan rep> 20000) jangan ragu untuk mengedit posting untuk memperbaiki kesalahan ketik dan kesalahan lainnya.

Prasoon Saurav
sumber
3
Alih-alih "asimetris", diurutkan sebelum / sesudah adalah hubungan "antisimetri". Ini harus diubah dalam teks agar sesuai dengan definisi urutan parsial yang diberikan kemudian (yang juga setuju dengan Wikipedia).
TemplateRex
1
Mengapa 7) item dalam contoh terakhir adalah UB? Mungkin seharusnya begitu f(i = -1, i = 1)?
Mikhail
1
Saya memperbaiki deskripsi hubungan "sequencing before". Ini adalah perintah parsial yang ketat . Jelas, suatu ekspresi tidak dapat diurutkan sebelum itu sendiri, sehingga hubungannya tidak bisa refleksif. Oleh karena itu asimetris bukan anti-simetris.
ThomasMcLeod
1
5) bersikap baik hati membuatku bingung. penjelasan dari Johannes Schaub tidak sepenuhnya mudah didapat. Terutama karena saya percaya bahwa bahkan dalam ++i(nilai dievaluasi sebelum +operator yang menggunakannya), standar masih tidak mengatakan bahwa efek sampingnya harus diselesaikan. Tetapi pada kenyataannya, karena mengembalikan ref ke lvalueyang itu isendiri, itu HARUS menyelesaikan efek samping karena evaluasi harus selesai, oleh karena itu nilainya harus up to date. Sebenarnya ini adalah bagian yang gila.
v.oddou
"Anggota Komite ISO C ++ berpikir bahwa hal-hal Sequence Points cukup sulit untuk dipahami. Jadi mereka memutuskan untuk menggantinya dengan hubungan yang disebutkan di atas hanya untuk kata-kata yang lebih jelas dan meningkatkan ketepatan." - apakah Anda punya referensi untuk klaim itu? Sepertinya bagi saya bahwa hubungan baru lebih sulit untuk dipahami.
MM
30

C ++ 17 ( N4659) termasuk proposal Refining Expression Evaluation Order untuk Idiomatic C ++ yang mendefinisikan urutan yang lebih ketat dari evaluasi ekspresi.

Secara khusus, kalimat berikut

8.18 Operator penugasan dan penugasan majemuk :
....

Dalam semua kasus, penugasan diurutkan setelah perhitungan nilai operan kanan dan kiri, dan sebelum perhitungan nilai ekspresi penugasan. Operan kanan diurutkan sebelum operan kiri.

bersama dengan klarifikasi berikut

Ekspresi X dikatakan diurutkan sebelum ekspresi Y jika setiap nilai perhitungan dan setiap efek samping yang berhubungan dengan ekspresi X Sekuensing sebelum setiap nilai perhitungan dan setiap efek samping yang berhubungan dengan ekspresi Y .

membuat beberapa kasus perilaku yang sebelumnya tidak terdefinisi valid, termasuk yang dipertanyakan:

a[++i] = i;

Namun beberapa kasus serupa lainnya masih mengarah pada perilaku yang tidak terdefinisi.

Dalam N4140:

i = i++ + 1; // the behavior is undefined

Tapi di N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Tentu saja, menggunakan kompiler yang memenuhi syarat C ++ 17 tidak harus berarti seseorang harus mulai menulis ekspresi seperti itu.

AlexD
sumber
mengapa i = i++ + 1;perilaku didefinisikan dalam c ++ 17, saya pikir bahkan jika "operan yang tepat diurutkan sebelum operan kiri", namun modifikasi untuk "i ++" dan efek samping untuk penugasan tidak dilanjutkan, tolong berikan rincian lebih lanjut untuk menafsirkan ini
jack X
@ jackX Saya memberikan jawabannya :).
AlexD
ya, saya pikir perincian interpretasi kalimat "Operan kanan diurutkan sebelum operan kiri" lebih berguna. Seperti "Operan kanan diurutkan sebelum operan kiri" berarti perhitungan nilai dan efek samping yang terkait dengan operan kanan adalah diurutkan sebelum operan kiri. seperti yang Anda lakukan :-)
jack X
11

Saya menduga ada alasan mendasar untuk perubahan itu, bukan sekadar kosmetik untuk membuat interpretasi yang lama menjadi lebih jelas: alasan itu adalah konkurensi. Urutan elaborasi yang tidak ditentukan hanyalah pemilihan salah satu dari beberapa urutan serial yang mungkin, ini sangat berbeda dengan sebelum dan setelah pemesanan, karena jika tidak ada pemesanan yang ditentukan, evaluasi bersamaan dimungkinkan: tidak demikian halnya dengan aturan lama. Misalnya di:

f (a,b)

sebelumnya a maka b, atau, b lalu a. Sekarang, a dan b dapat dievaluasi dengan instruksi yang disisipkan atau bahkan pada core yang berbeda.

Yttrill
sumber
5
Saya percaya, bahwa jika 'a' atau 'b' termasuk pemanggilan fungsi, mereka secara tidak pasti diurutkan alih-alih tanpa penguraian, yang mengatakan bahwa semua efek samping dari satu diperlukan untuk terjadi sebelum efek samping dari lain, meskipun kompiler tidak perlu konsisten tentang yang mana yang lebih dulu. Jika itu tidak lagi benar, itu akan memecah banyak kode yang bergantung pada operasi yang tidak tumpang tindih (misalnya jika 'a' dan 'b' masing-masing mengatur, menggunakan, dan mencatat, keadaan statis bersama).
supercat
2

Di C99(ISO/IEC 9899:TC3)mana tampaknya absen dari diskusi ini sejauh ini steteents berikut dibuat mengenai urutan evaluasi.

[...] urutan evaluasi subekspresi dan urutan di mana efek samping terjadi keduanya tidak ditentukan. (Bagian 6.5 hal 67)

Urutan evaluasi operan tidak ditentukan. Jika suatu upaya dilakukan untuk memodifikasi hasil dari operator penugasan atau untuk mengaksesnya setelah titik urutan berikutnya, perilaku [sic] tidak terdefinisi (Bagian 6.5.16 hal 91)

awiebe
sumber
2
Pertanyaannya ditandai C ++ dan bukan C, yang baik karena perilaku di C ++ 17 sangat berbeda dari perilaku di versi yang lebih lama - dan tidak ada kaitannya dengan perilaku di C11, C99, C90, dll. Atau beruang sangat sedikit hubungannya dengan itu. Secara keseluruhan, saya sarankan menghapus ini. Lebih penting lagi, kita perlu menemukan Q&A yang setara untuk C dan memastikan itu OK (dan mencatat bahwa C ++ 17, khususnya, mengubah aturan - perilaku di C ++ 11 dan sebelumnya kurang lebih sama dengan di C11, meskipun kata-kata yang menggambarkannya dalam C masih menggunakan 'titik sekuens' sedangkan C ++ 11 dan kemudian tidak.
Jonathan Leffler