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 postfix x++
. Tetapi operator ini sulit untuk dipikirkan, jadi untuk memastikan Anda mengerti, mungkin Anda menulis sebuah program uji kecil yang melibatkan sesuatu seperti
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
Tetapi, 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
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
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
printf("%d %d %d\n", x, ++x, x++);
pertanyaannya adalah, sebelum memanggil printf
, apakah kompiler menghitung nilai x
pertama, atau x++
, 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 melakukan x
pertama, kemudian ++x
, kemudian x++
, atau x++
kemudian ++x
kemudian x
, atau beberapa urutan lainnya. Tetapi urutan jelas penting, karena bergantung pada urutan yang digunakan kompiler, kami akan jelas mendapatkan hasil yang berbeda dicetak oleh printf
.
Bagaimana dengan ekspresi gila ini?
x = x++ + ++x;
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 di x
, dan mengembalikan nilai lama dari x
; (2) ++x
bagian mencoba menambahkan 1 ke x, menyimpan nilai baru x
, dan mengembalikan nilai baru x
; 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 ditugaskan x
? 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:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
Semua ungkapan ini tidak terdefinisi:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
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:
- Jika ada satu variabel yang dimodifikasi (ditugaskan) di dua atau lebih tempat yang berbeda, bagaimana Anda tahu modifikasi mana yang terjadi pertama kali?
- Jika ada variabel yang dimodifikasi di satu tempat, dan nilainya digunakan di tempat lain, bagaimana Anda tahu apakah itu menggunakan nilai lama atau nilai baru?
Sebagai contoh # 1, dalam ekspresi
x = x++ + ++x;
ada tiga upaya untuk memodifikasi `x.
Sebagai contoh # 2, dalam ekspresi
y = x + x++;
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.