#include <stdio.h>
int main(void)
{
int i = 0;
i = i++ + ++i;
printf("%d\n", i); // 3
i = 1;
i = (i++);
printf("%d\n", i); // 2 Should be 1, no ?
volatile int u = 0;
u = u++ + ++u;
printf("%d\n", u); // 1
u = 1;
u = (u++);
printf("%d\n", u); // 2 Should also be one, no ?
register int v = 0;
v = v++ + ++v;
printf("%d\n", v); // 3 (Should be the same as u ?)
int w = 0;
printf("%d %d\n", ++w, w); // shouldn't this print 1 1
int x[2] = { 5, 8 }, y = 0;
x[y] = y ++;
printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}
815
(i++)
masih dievaluasi menjadi 1, terlepas dari tanda kurungi = (i++);
yang dimaksudkan untuk dilakukan, pasti ada cara yang lebih jelas untuk menulisnya. Itu akan benar bahkan jika itu didefinisikan dengan baik. Bahkan di Jawa, yang mendefinisikan perilakui = (i++);
, itu masih kode yang buruk. Tulis sajai++;
Jawaban:
C memiliki konsep perilaku tidak terdefinisi, yaitu beberapa konstruksi bahasa sah secara sintaksis tetapi Anda tidak dapat memprediksi perilaku ketika kode dijalankan.
Sejauh yang saya tahu, standar itu tidak secara eksplisit mengatakan mengapa konsep perilaku tidak terdefinisi ada. Dalam pikiran saya, itu hanya karena perancang bahasa ingin ada beberapa kelonggaran dalam semantik, bukannya mengharuskan semua implementasi menangani bilangan bulat bilangan bulat dengan cara yang sama persis, yang sangat mungkin akan menimbulkan biaya kinerja yang serius, mereka hanya meninggalkan perilaku tidak terdefinisi sehingga jika Anda menulis kode yang menyebabkan integer overflow, apa pun bisa terjadi.
Jadi, dengan itu dalam pikiran, mengapa "masalah" ini? Bahasa tersebut dengan jelas mengatakan bahwa hal-hal tertentu mengarah pada perilaku yang tidak terdefinisi . Tidak ada masalah, tidak ada "harus" terlibat. Jika perilaku tidak terdefinisi berubah ketika salah satu variabel yang terlibat dideklarasikan
volatile
, itu tidak membuktikan atau mengubah apa pun. Itu tidak terdefinisi ; Anda tidak dapat memberi alasan tentang perilaku tersebut.Contoh Anda yang paling menarik, contohnya
adalah contoh buku teks dari perilaku yang tidak terdefinisi (lihat entri Wikipedia tentang poin urutan ).
sumber
i = ++i + 1;
.Hanya kompilasi dan bongkar baris kode Anda, jika Anda sangat ingin tahu bagaimana tepatnya Anda mendapatkan apa yang Anda dapatkan.
Inilah yang saya dapatkan di mesin saya, bersama dengan apa yang saya pikir sedang terjadi:
(Saya ... misalkan instruksi 0x00000014 adalah semacam optimisasi kompiler?)
sumber
gcc evil.c -c -o evil.bin
dangdb evil.bin
→disassemble evil
, atau apa pun yang setara dengan Windows :)Why are these constructs undefined behavior?
.gcc -S evil.c
), yang hanya itu yang dibutuhkan di sini. Merakit kemudian membongkar itu hanyalah cara bundaran untuk melakukannya.Saya pikir bagian yang relevan dari standar C99 adalah 6,5 Ekspresi, §2
dan 6.5.16 Operator penugasan, §4:
sumber
i=i=5
adalah perilaku yang tidak terdefinisiA=B=5;
"Write-lock A; Write-Lock B; Store 5 to A; store 5 to B; store 5 to B; Unlock B ; Buka Kunci A; ", dan pernyataan sepertiC=A+B;
" Baca-kunci A; Baca-kunci B; Hitung A + B; Buka kunci A dan B; Tulis-kunci C; Hasil penyimpanan; Buka Kunci C; ". Itu akan memastikan bahwa jika satu utas melakukanA=B=5;
sedangkan utas lainnya lakukanC=A+B;
maka utas yang kedua akan melihat keduanya menulis sebagai telah terjadi atau tidak. Berpotensi jaminan yang bermanfaat.I=I=5;
Namun, jika satu utas melakukannya , ...Sebagian besar jawaban di sini dikutip dari standar C yang menekankan bahwa perilaku konstruksi ini tidak ditentukan. Untuk memahami mengapa perilaku konstruksi ini tidak terdefinisi , mari kita pahami istilah-istilah ini terlebih dahulu mengingat standar C11:
Diurutkan: (5.1.2.3)
Tidak dilanjutkan:
Evaluasi dapat menjadi salah satu dari dua hal:
Titik Urutan:
Sekarang sampai pada pertanyaan, untuk ungkapan suka
standar mengatakan bahwa:
6.5 Ekspresi:
Oleh karena itu, ungkapan di atas memanggil UB karena dua efek samping pada objek yang sama
i
tidak saling berhubungan satu sama lain. Itu berarti tidak diurutkan apakah efek samping dengan tugasi
akan dilakukan sebelum atau setelah efek samping oleh++
.Bergantung pada apakah penugasan terjadi sebelum atau setelah kenaikan, hasil yang berbeda akan dihasilkan dan itulah salah satu kasus perilaku tidak terdefinisi .
Mari kita ganti nama menjadi
i
di sebelah kiri penugasanil
dan di sebelah kanan penugasan (dalam ekspresii++
) menjadiir
, maka ekspresi menjadi sepertiPoin penting mengenai
++
operator Postfix adalah:Itu artinya ungkapan
il = ir++
dapat dievaluasi sebagaiatau
menghasilkan dua hasil yang berbeda
1
dan2
yang tergantung pada urutan efek samping berdasarkan penugasan++
dan karenanya memanggil UB.sumber
Perilaku tidak dapat benar-benar dijelaskan karena memunculkan perilaku yang tidak ditentukan dan perilaku tidak terdefinisi , jadi kami tidak dapat membuat prediksi umum tentang kode ini, meskipun jika Anda membaca karya Olve Maudal seperti Deep C dan Unspecified dan Undefined terkadang Anda dapat membuat yang baik menebak dalam kasus yang sangat spesifik dengan kompiler dan lingkungan spesifik tetapi tolong jangan lakukan itu di dekat produksi.
Jadi beralih ke perilaku yang tidak ditentukan , dalam konsep c99 bagian standar
6.5
ayat 3 mengatakan ( penekanan milikku ):Jadi ketika kita memiliki garis seperti ini:
kita tidak tahu apakah
i++
atau++i
akan dievaluasi pertama. Ini terutama untuk memberikan kompiler opsi yang lebih baik untuk optimisasi .Kami juga memiliki perilaku undefined sini juga karena program ini memodifikasi variabel (
i
,u
, dll ..) lebih dari sekali antara urutan poin . Dari rancangan bagian standar6.5
ayat 2 ( penekanan ):itu mengutip contoh kode berikut sebagai tidak terdefinisi:
Dalam semua contoh ini kode berusaha untuk memodifikasi objek lebih dari sekali di titik urutan yang sama, yang akan diakhiri dengan
;
di masing-masing kasus ini:Perilaku yang tidak ditentukan ditentukan dalam rancangan standar c99 di bagian
3.4.4
sebagai:dan perilaku tidak terdefinisi didefinisikan pada bagian
3.4.3
sebagai:dan perhatikan bahwa:
sumber
Cara lain untuk menjawab ini, alih-alih terjebak dalam detail-detail misterius dari titik-titik urutan dan perilaku yang tidak terdefinisi, adalah dengan bertanya, apa artinya? Apa yang coba dilakukan oleh programmer?
Fragmen pertama yang ditanyakan tentang,,
i = i++ + ++i
cukup jelas gila di buku saya. Tidak seorang pun akan menulisnya dalam program nyata, tidak jelas apa fungsinya, tidak ada algoritma yang dapat dipikirkan seseorang yang bisa mencoba kode yang akan menghasilkan urutan operasi yang dibuat khusus ini. Dan karena tidak jelas bagi Anda dan saya apa yang harus dilakukan, tidak apa-apa dalam buku saya jika kompiler tidak dapat mengetahui apa yang seharusnya dilakukan.Fragmen kedua,,
i = i++
sedikit lebih mudah dimengerti. Seseorang jelas berusaha untuk meningkatkan i, dan menetapkan hasilnya kembali ke i. Tetapi ada beberapa cara melakukan ini dalam C. Cara paling dasar untuk menambahkan 1 ke i, dan menetapkan hasilnya kembali ke i, adalah sama di hampir semua bahasa pemrograman:C, tentu saja, memiliki pintasan praktis:
Ini berarti, "tambahkan 1 ke i, dan tetapkan hasilnya kembali ke i". Jadi jika kita membangun gado-gado dari keduanya, dengan menulis
apa yang sebenarnya kami katakan adalah "tambahkan 1 ke saya, dan tetapkan hasilnya kembali ke saya, dan tetapkan hasilnya kembali ke saya". Kami bingung, jadi tidak terlalu mengganggu saya jika kompiler juga bingung.
Secara realistis, satu-satunya saat ekspresi gila ini ditulis adalah ketika orang menggunakannya sebagai contoh buatan bagaimana ++ seharusnya bekerja. Dan tentu saja penting untuk memahami bagaimana ++ bekerja. Tetapi satu aturan praktis untuk menggunakan ++ adalah, "Jika tidak jelas apa arti ungkapan menggunakan ++, jangan tulis itu."
Kami dulu menghabiskan banyak waktu di comp.lang.c untuk membahas ekspresi seperti ini dan mengapa mereka tidak terdefinisi. Dua jawaban saya yang lebih panjang, yang mencoba menjelaskan mengapa, diarsipkan di web:
Lihat juga mempertanyakan 3.8 dan sisanya dari pertanyaan-pertanyaan di bagian 3 dari daftar C FAQ .
sumber
*p=(*q)++;
untuk berartiif (p!=q) *p=(*q)++; else *p= __ARBITRARY_VALUE;
yang tidak lagi menjadi masalah. Hyper-modern C akan membutuhkan penulisan sesuatu seperti formulasi yang terakhir (walaupun tidak ada cara standar untuk menunjukkan kode tidak peduli apa yang ada di dalamnya*p
) untuk mencapai tingkat efisiensi yang digunakan kompiler dengan yang sebelumnya (else
klausa diperlukan untuk membiarkan kompiler mengoptimalkan apaif
yang dibutuhkan beberapa kompiler baru).assert
pernyataan, sehingga programmer dapat mendahului baris yang dimaksud dengan sederhanaassert(p != q)
. (Tentu saja, mengambil kursus itu juga akan membutuhkan penulisan ulang<assert.h>
untuk tidak langsung menghapus pernyataan dalam versi non-debug, melainkan mengubahnya menjadi sesuatu seperti__builtin_assert_disabled()
yang dapat dilihat oleh kompiler, dan kemudian tidak memancarkan kode.)Seringkali pertanyaan ini ditautkan sebagai duplikat dari pertanyaan yang terkait dengan kode suka
atau
atau varian serupa.
Sementara ini juga perilaku yang tidak terdefinisi sebagaimana telah dinyatakan, ada perbedaan halus ketika
printf()
terlibat ketika membandingkan dengan pernyataan seperti:Dalam pernyataan berikut:
yang urutan evaluasi dari argumen dalam
printf()
adalah tidak ditentukan . Itu berarti, ekspresii++
dan++i
dapat dievaluasi dalam urutan apa pun. Standar C11 memiliki beberapa deskripsi yang relevan tentang ini:Lampiran J, perilaku yang tidak ditentukan
3.4.4, perilaku yang tidak ditentukan
The perilaku yang tidak ditentukan sendiri tidak masalah. Pertimbangkan contoh ini:
Ini juga memiliki perilaku yang tidak ditentukan karena urutan evaluasi
++x
dany++
tidak ditentukan. Tapi itu pernyataan yang sah dan sah. Tidak ada perilaku tidak terdefinisi dalam pernyataan ini. Karena modifikasi (++x
dany++
) dilakukan untuk objek yang berbeda .Apa yang membuat pernyataan berikut
sebagai perilaku yang tidak terdefinisi adalah kenyataan bahwa dua ekspresi ini memodifikasi objek yang sama
i
tanpa titik urutan intervensi .Detail lainnya adalah bahwa koma yang terlibat dalam panggilan printf () adalah pemisah , bukan operator koma .
Ini adalah perbedaan penting karena operator koma memperkenalkan titik sekuens antara evaluasi operan mereka, yang membuat hukum berikut ini:
Operator koma mengevaluasi operannya dari kiri ke kanan dan hanya menghasilkan nilai operan terakhir. Jadi
j = (++i, i++);
,++i
kenaikani
untuk6
dani++
hasil nilai lamai
(6
) yang ditugaskan untukj
. Kemudiani
menjadi7
karena kenaikan.Jadi jika koma dalam panggilan fungsi itu menjadi operator koma maka
tidak akan menjadi masalah. Tetapi ini memunculkan perilaku yang tidak terdefinisi karena koma di sini adalah pemisah .
Bagi mereka yang baru mengenal perilaku yang tidak terdefinisi akan mendapat manfaat dari membaca Apa yang Harus Diketahui Setiap Programer C Tentang Perilaku yang Tidak Terdefinisi untuk memahami konsep dan banyak varian lain dari perilaku tidak terdefinisi dalam C.
Posting ini: Perilaku tidak terdefinisi, tidak spesifik, dan terdefinisi implementasi juga relevan.
sumber
int a = 10, b = 20, c = 30; printf("a=%d b=%d c=%d\n", (a = a + b + c), (b = b + b), (c = c + c));
tampaknya memberikan perilaku yang stabil (evaluasi argumen kanan-ke-kiri di gcc v7.3.0; hasil "a = 110 b = 40 c = 60"). Apakah karena penugasan dianggap sebagai 'pernyataan lengkap' dan dengan demikian memperkenalkan titik urutan? Tidakkah hal itu menghasilkan evaluasi argumen / pernyataan dari kiri ke kanan? Atau, apakah itu hanya manifestasi dari perilaku yang tidak terdefinisi?b
danc
dalam argumen 3 & 4 dan membaca argumen 2. Tetapi tidak ada urutan di antara ekspresi ini (argumen 2, 3, & 4). gcc / dentang memiliki opsi-Wsequence-point
yang dapat membantu menemukan ini juga.Meskipun tidak mungkin bahwa kompiler dan prosesor benar-benar melakukannya, itu akan legal, di bawah standar C, bagi kompiler untuk mengimplementasikan "i ++" dengan urutan:
Walaupun saya tidak berpikir ada prosesor yang mendukung perangkat keras untuk memungkinkan hal seperti itu dilakukan secara efisien, orang dapat dengan mudah membayangkan situasi di mana perilaku seperti itu akan membuat kode multi-threaded lebih mudah (misalnya itu akan menjamin bahwa jika dua thread mencoba untuk melakukan hal di atas) urutan secara bersamaan,
i
akan bertambah dua) dan itu tidak sepenuhnya tak terbayangkan bahwa beberapa prosesor masa depan mungkin menyediakan fitur sesuatu seperti itu.Jika kompiler harus menulis
i++
seperti yang ditunjukkan di atas (legal di bawah standar) dan harus menyelingi instruksi di atas selama evaluasi keseluruhan ekspresi (juga legal), dan jika tidak kebetulan melihat bahwa salah satu instruksi lain terjadi untuk mengaksesi
, dimungkinkan (dan legal) bagi kompiler untuk membuat urutan instruksi yang akan menemui jalan buntu. Yang pasti, kompiler hampir pasti akan mendeteksi masalah dalam kasus di mana variabel yang samai
digunakan di kedua tempat, tetapi jika rutin menerima referensi ke dua pointerp
danq
, dan menggunakan(*p)
dan dua kali) kompiler tidak akan diminta untuk mengenali atau menghindari kebuntuan yang akan terjadi jika alamat objek yang sama diteruskan untuk keduanya dan(*q)
dalam ekspresi di atas (daripada menggunakani
p
q
.sumber
Sementara sintaks ekspresi suka
a = a++
ataua++ + a++
legal, perilaku konstruk ini tidak ditentukan karena harus dalam standar C tidak dipatuhi. C99 6.5p2 :Dengan catatan kaki 73 semakin memperjelas hal itu
Berbagai titik urutan tercantum dalam Lampiran C C11 (dan C99 ):
Kata-kata dari paragraf yang sama dalam C11 adalah:
Anda dapat mendeteksi kesalahan seperti itu dalam suatu program dengan misalnya menggunakan versi terbaru GCC dengan
-Wall
dan-Werror
, dan kemudian GCC akan langsung menolak untuk mengkompilasi program Anda. Berikut ini adalah output dari gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:Bagian yang penting adalah mengetahui apa itu titik sekuensial - dan apa itu titik sekuensial dan apa yang tidak . Misalnya operator koma adalah titik urutan, jadi
didefinisikan dengan baik, dan akan bertambah
i
satu, menghasilkan nilai lama, membuang nilai itu; kemudian pada operator koma, menyelesaikan efek samping; dan kemudian bertambahi
satu, dan nilai yang dihasilkan menjadi nilai ekspresi - yaitu ini hanya cara yang dibuat untuk menulisj = (i += 2)
yang sekali lagi merupakan cara "pintar" untuk menulisNamun,
,
daftar argumen fungsi di bukan operator koma, dan tidak ada titik urutan antara evaluasi argumen yang berbeda; alih-alih evaluasi mereka tidak dilanjutkan berkenaan dengan satu sama lain; begitu panggilan fungsimemiliki perilaku yang tidak terdefinisi karena tidak ada titik urutan antara evaluasi
i++
dan++i
dalam argumen fungsi , dan nilaii
karenanya dimodifikasi dua kali, oleh keduanyai++
dan++i
, antara titik urutan sebelumnya dan berikutnya.sumber
Standar C mengatakan bahwa variabel hanya boleh ditugaskan paling banyak sekali antara dua titik urutan. Semi-kolon misalnya adalah titik urutan.
Jadi setiap pernyataan dalam bentuk:
dan seterusnya melanggar aturan itu. Standar ini juga mengatakan bahwa perilaku tidak terdefinisi dan tidak ditentukan. Beberapa kompiler mendeteksi ini dan menghasilkan beberapa hasil tetapi ini tidak sesuai standar.
Namun, dua variabel yang berbeda dapat ditingkatkan antara dua titik urutan.
Di atas adalah praktik pengkodean yang umum saat menyalin / menganalisis string.
sumber
Dalam /programming/29505280/incrementing-array-index-in-c seseorang bertanya tentang pernyataan seperti:
yang mencetak 7 ... OP mengharapkannya untuk mencetak 6.
The
++i
bertahap tidak dijamin untuk semua lengkap sebelum sisa perhitungan. Bahkan, kompiler yang berbeda akan mendapatkan hasil yang berbeda di sini. Dalam contoh yang Anda berikan, pertama 2++i
dieksekusi, maka nilai-nilaik[]
yang dibaca, maka yang terakhir++i
ituk[]
.Kompiler modern akan mengoptimalkan ini dengan sangat baik. Bahkan, mungkin lebih baik daripada kode yang Anda tulis semula (dengan asumsi kode itu bekerja seperti yang Anda harapkan).
sumber
Penjelasan yang baik tentang apa yang terjadi dalam jenis perhitungan ini disediakan dalam dokumen n1188 dari situs ISO W14 .
Saya menjelaskan ide-idenya.
Aturan utama dari standar ISO 9899 yang berlaku dalam situasi ini adalah 6.5p2.
Urutan poin dalam ekspresi seperti
i=i++
adalah sebelumi=
dan sesudahi++
.Dalam makalah yang saya kutip di atas dijelaskan bahwa Anda dapat mengetahui program sebagai dibentuk oleh kotak-kotak kecil, setiap kotak berisi instruksi antara 2 titik urutan berturut-turut. Titik-titik urutan didefinisikan dalam lampiran C dari standar, dalam hal
i=i++
ada 2 titik urutan yang membatasi ekspresi penuh. Ungkapan seperti itu secara sintaksis setara dengan entriexpression-statement
dalam bentuk Backus-Naur dari tata bahasa (tata bahasa disediakan dalam lampiran A dari Standar).Jadi urutan instruksi di dalam kotak tidak memiliki urutan yang jelas.
dapat diartikan sebagai
atau sebagai
karena kedua bentuk ini untuk menafsirkan kode
i=i++
itu valid dan karena keduanya menghasilkan jawaban yang berbeda, perilaku tidak terdefinisi.Jadi titik urutan dapat dilihat oleh awal dan akhir setiap kotak yang menyusun program [kotak adalah unit atom dalam C] dan di dalam kotak urutan instruksi tidak didefinisikan dalam semua kasus. Mengubah urutan itu seseorang kadang-kadang dapat mengubah hasilnya.
EDIT:
Sumber bagus lainnya untuk menjelaskan ambiguitas tersebut adalah entri dari situs c-faq (juga diterbitkan sebagai buku ), yaitu di sini dan di sini dan di sini .
sumber
i=i++
sangat mirip dengan jawaban ini .Pertanyaan Anda mungkin bukan, "Mengapa konstruk ini perilaku yang tidak terdefinisi dalam C?". Pertanyaan Anda mungkin, "Mengapa kode ini (menggunakan
++
) tidak memberi saya nilai yang saya harapkan?", Dan seseorang menandai pertanyaan Anda sebagai duplikat, dan mengirim Anda ke sini.IniJawaban mencoba menjawab pertanyaan itu: mengapa kode Anda tidak memberikan jawaban yang Anda harapkan, dan bagaimana Anda bisa belajar mengenali (dan menghindari) ekspresi yang tidak akan berfungsi seperti yang diharapkan.
Saya berasumsi Anda telah mendengar definisi dasar C
++
dan--
operator sekarang, dan bagaimana bentuk awalan++x
berbeda dari bentuk postfixx++
. Tetapi operator ini sulit untuk dipikirkan, jadi untuk memastikan Anda mengerti, mungkin Anda menulis sebuah program uji kecil yang melibatkan sesuatu sepertiTetapi, yang mengejutkan Anda, program ini tidak membantu Anda memahami - program ini mencetak beberapa hasil yang aneh, tak terduga, dan tidak dapat dijelaskan, menunjukkan bahwa mungkin
++
melakukan sesuatu yang sama sekali berbeda, sama sekali tidak seperti yang Anda pikirkan.Atau, mungkin Anda sedang melihat ekspresi yang sulit dipahami seperti
Mungkin seseorang memberi Anda kode itu sebagai teka-teki. Kode ini juga tidak masuk akal, terutama jika Anda menjalankannya - dan jika Anda mengkompilasi dan menjalankannya di bawah dua kompiler yang berbeda, Anda kemungkinan akan mendapatkan dua jawaban yang berbeda! Ada apa dengan itu? Jawaban mana yang benar? (Dan jawabannya adalah keduanya, atau tidak keduanya.)
Seperti yang sudah Anda dengar sekarang, semua ungkapan ini tidak terdefinisi , yang berarti bahwa bahasa C tidak menjamin apa yang akan mereka lakukan. Ini adalah hasil yang aneh dan mengejutkan, karena Anda mungkin berpikir bahwa program apa pun yang dapat Anda tulis, asalkan dikompilasi dan dijalankan, akan menghasilkan output yang unik dan terdefinisi dengan baik. Tetapi dalam kasus perilaku tidak terdefinisi, itu tidak benar.
Apa yang membuat ekspresi tidak terdefinisi? Apakah ekspresi melibatkan
++
dan--
selalu tidak terdefinisi? Tentu saja tidak: ini adalah operator yang berguna, dan jika Anda menggunakannya dengan benar, mereka didefinisikan dengan sangat baik.Untuk ekspresi yang kita bicarakan adalah apa yang membuat mereka tidak terdefinisi adalah ketika ada terlalu banyak hal yang terjadi sekaligus, ketika kita tidak yakin urutan apa yang akan terjadi, tetapi ketika urutan penting bagi hasil yang kita dapatkan.
Mari kita kembali ke dua contoh yang saya gunakan dalam jawaban ini. Ketika saya menulis
pertanyaannya adalah, sebelum memanggil
printf
, apakah kompiler menghitung nilaix
pertama, ataux++
, atau mungkin++x
? Tapi ternyata kita tidak tahu . Tidak ada aturan dalam C yang mengatakan bahwa argumen untuk suatu fungsi dievaluasi dari kiri ke kanan, atau kanan ke kiri, atau dalam urutan lain. Jadi kita tidak bisa mengatakan apakah compiler akan melakukanx
pertama, kemudian++x
, kemudianx++
, ataux++
kemudian++x
kemudianx
, atau beberapa urutan lainnya. Tetapi urutan jelas penting, karena bergantung pada urutan yang digunakan kompiler, kami akan jelas mendapatkan hasil yang berbeda dicetak olehprintf
.Bagaimana dengan ekspresi gila ini?
Masalah dengan ungkapan ini adalah bahwa ia mengandung tiga upaya berbeda untuk mengubah nilai x: (1)
x++
bagian mencoba menambahkan 1 ke x, menyimpan nilai baru dix
, dan mengembalikan nilai lama darix
; (2)++x
bagian mencoba menambahkan 1 ke x, menyimpan nilai barux
, dan mengembalikan nilai barux
; dan (3)x =
bagian mencoba untuk menetapkan jumlah dari dua lainnya kembali ke x. Manakah dari tiga tugas percobaan yang akan "menang"? Manakah dari tiga nilai yang benar-benar akan ditugaskanx
? Sekali lagi, dan mungkin secara mengejutkan, tidak ada aturan dalam C untuk memberi tahu kami.Anda mungkin membayangkan bahwa prioritas atau asosiatif atau evaluasi kiri-ke-kanan memberi tahu Anda urutan hal-hal yang terjadi, tetapi tidak. Anda mungkin tidak mempercayai saya, tapi tolong ambil kata saya untuk itu, dan saya akan mengatakannya lagi: diutamakan dan asosiatifitas tidak menentukan setiap aspek dari urutan evaluasi ekspresi dalam C. Secara khusus, jika dalam satu ekspresi ada banyak tempat-tempat berbeda di mana kami mencoba menetapkan nilai baru untuk sesuatu seperti
x
, diutamakan dan asosiatif tidak memberi tahu kami upaya mana yang terjadi pertama, atau terakhir, atau apa pun.Jadi dengan semua latar belakang dan pengantar itu, jika Anda ingin memastikan bahwa semua program Anda terdefinisi dengan baik, ekspresi mana yang dapat Anda tulis, dan mana yang tidak bisa Anda tulis?
Semua ungkapan ini baik-baik saja:
Semua ungkapan ini tidak terdefinisi:
Dan pertanyaan terakhir adalah, bagaimana Anda bisa tahu ekspresi mana yang didefinisikan dengan baik, dan ekspresi mana yang tidak ditentukan?
Seperti yang saya katakan sebelumnya, ekspresi yang tidak terdefinisi adalah ekspresi di mana ada terlalu banyak hal yang terjadi sekaligus, di mana Anda tidak dapat memastikan di mana urutan hal-hal terjadi, dan di mana urutannya:
Sebagai contoh # 1, dalam ekspresi
ada tiga upaya untuk memodifikasi `x.
Sebagai contoh # 2, dalam ekspresi
kami berdua menggunakan nilai
x
, dan memodifikasinya.Jadi itulah jawabannya: pastikan bahwa dalam ekspresi apa pun yang Anda tulis, setiap variabel diubah paling banyak satu kali, dan jika variabel diubah, Anda juga tidak berusaha menggunakan nilai variabel itu di tempat lain.
sumber
Alasannya adalah bahwa program ini menjalankan perilaku yang tidak terdefinisi. Masalahnya terletak pada urutan evaluasi, karena tidak ada titik urutan yang diperlukan sesuai dengan standar C ++ 98 (tidak ada operasi yang diurutkan sebelum atau sesudah yang lain sesuai dengan terminologi C ++ 11).
Namun jika Anda tetap menggunakan satu kompiler, Anda akan menemukan perilaku tersebut tetap ada, selama Anda tidak menambahkan pemanggilan fungsi atau petunjuk, yang akan membuat perilaku tersebut lebih berantakan.
Jadi pertama-tama GCC: Menggunakan Nuwen MinGW 15 GCC 7.1 Anda akan mendapatkan:
}
Bagaimana cara kerja GCC? ia mengevaluasi sub ekspresi pada urutan kiri ke kanan untuk sisi kanan (RHS), kemudian memberikan nilai ke sisi kiri (LHS). Inilah tepatnya bagaimana Java dan C # berperilaku dan mendefinisikan standar mereka. (Ya, perangkat lunak yang setara di Java dan C # telah mendefinisikan perilaku). Ini mengevaluasi setiap sub ekspresi satu per satu dalam Pernyataan RHS dalam urutan kiri ke kanan; untuk setiap sub ekspresi: ++ c (pra-kenaikan) dievaluasi terlebih dahulu kemudian nilai c digunakan untuk operasi, lalu kenaikan pos c ++).
menurut GCC C ++: Operator
kode setara dalam perilaku yang didefinisikan C ++ sebagaimana dipahami GCC:
Lalu kita pergi ke Visual Studio . Visual Studio 2015, Anda mendapatkan:
Bagaimana cara kerja studio visual, dibutuhkan pendekatan lain, ia mengevaluasi semua ekspresi pra-kenaikan di lintasan pertama, kemudian menggunakan nilai variabel dalam operasi di lintasan kedua, menetapkan dari RHS ke LHS di lintasan ketiga, lalu pada lintasan terakhir ia mengevaluasi semua ekspresi pasca kenaikan dalam satu pass.
Jadi, ekuivalen dalam perilaku yang didefinisikan C ++ sebagai Visual C ++ mengerti:
sebagaimana dinyatakan oleh dokumentasi Visual Studio di Precedence and Order of Evaluation :
sumber