Apa yang saya = (i, ++ i, 1) + 1; melakukan?

174

Setelah membaca jawaban ini tentang perilaku dan urutan poin yang tidak ditentukan, saya menulis sebuah program kecil:

#include <stdio.h>

int main(void) {
  int i = 5;
  i = (i, ++i, 1) + 1;
  printf("%d\n", i);
  return 0;
}

Outputnya adalah 2. Ya Tuhan, aku tidak melihat penurunan itu datang! Apa yang terjadi disini?

Juga, saat mengkompilasi kode di atas, saya mendapat peringatan yang mengatakan:

px.c: 5: 8: peringatan: operan kiri dari ekspresi koma tidak berpengaruh

  [-Wunused-value]   i = (i, ++i, 1) + 1;
                        ^

Mengapa? Tetapi mungkin itu akan secara otomatis dijawab dengan jawaban dari pertanyaan pertama saya.

gsamaras
sumber
289
Jangan lakukan hal-hal aneh, Anda tidak akan punya teman :(
Maroun
9
Pesan peringatan adalah jawaban untuk pertanyaan pertama Anda.
Yu Hao
2
@gsamaras: nggak. yang dihasilkan nilai dibuang, tidak modifikasi. jawaban sebenarnya: operator koma menciptakan titik urutan.
Karoly Horvath
3
@gsamaras Anda seharusnya tidak peduli ketika Anda memiliki skor positif dan bahkan lebih dengan 10 pertanyaan.
LyingOnTheSky
9
Catatan: Kompilator pengoptimal mungkin sederhanaprintf("2\n");
chux - Reinstate Monica

Jawaban:

256

Dalam ekspresi (i, ++i, 1), koma yang digunakan adalah operator koma

operator koma (diwakili oleh token ,) adalah operator biner yang mengevaluasi operan pertamanya dan membuang hasilnya, dan kemudian mengevaluasi operan kedua dan mengembalikan nilai ini (dan tipe).

Karena membuang operan pertamanya, umumnya hanya berguna jika operan pertama memiliki efek samping yang diinginkan . Jika efek samping ke operan pertama tidak terjadi, maka kompiler dapat menghasilkan peringatan tentang ekspresi tanpa efek.

Jadi, dalam ungkapan di atas, yang paling kiri iakan dievaluasi dan nilainya akan dibuang. Kemudian ++iakan dievaluasi dan akan bertambah i1 dan lagi nilai ekspresi ++iakan dibuang, tetapi efek sampingnya ipermanen . Kemudian 1akan dievaluasi dan nilai ekspresi akan 1.

Ini setara dengan

i;          // Evaluate i and discard its value. This has no effect.
++i;        // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;  

Perhatikan bahwa ungkapan di atas adalah benar-benar valid dan tidak memunculkan perilaku tidak terdefinisi karena ada titik sekuen antara evaluasi operan kiri dan kanan operator koma.

haccks
sumber
1
meskipun ekspresi akhir valid, bukankah ekspresi kedua ++ i merupakan perilaku yang tidak terdefinisi? itu dievaluasi dan nilai variabel tidak diinisialisasi adalah pra-kenaikan, yang tidak benar? atau saya kehilangan sesuatu?
Koushik Shetty
2
@Koushik; idiinisialisasi dengan 5. Lihatlah pernyataan deklarasi int i = 5;.
jam
1
oh salahku Maaf saya jujur ​​tidak melihat itu.
Koushik Shetty
Ada kesalahan di sini: ++ saya akan menambah saya kemudian mengevaluasinya, sedangkan saya ++ akan mengevaluasi saya kemudian menambahnya.
Quentin Hayot
1
@QuentinHayot; Apa? Setiap efek samping terjadi setelah evaluasi ekspresi. Dalam hal ++i, ungkapan ini akan dievaluasi, iakan bertambah dan nilai kenaikan ini akan menjadi nilai ekspresi. Dalam hal i++, ekspresi ini akan dievaluasi, nilai lama iakan menjadi nilai ekspresi, iakan bertambah setiap saat antara titik urutan sebelumnya dan berikutnya dari ekspresi.
jam
62

Mengutip dari C11, bab 6.5.17, operator Koma

Operan kiri dari operator koma dievaluasi sebagai ekspresi batal; ada titik urutan antara evaluasinya dan operan yang tepat. Kemudian operan yang tepat dievaluasi; hasilnya memiliki jenis dan nilai.

Jadi, dalam kasus Anda,

(i, ++i, 1)

dievaluasi sebagai

  1. i, akan dievaluasi sebagai ekspresi batal, nilai dibuang
  2. ++i, akan dievaluasi sebagai ekspresi batal, nilai dibuang
  3. Akhirnya,, 1nilai kembali.

Jadi, pernyataan terakhirnya seperti

i = 1 + 1;

dan isampai 2. Saya kira ini menjawab kedua pertanyaan Anda,

  • Bagaimana imendapat nilai 2?
  • Mengapa ada pesan peringatan?

Catatan: FWIW, karena ada titik sekuens yang hadir setelah evaluasi operan kiri, ungkapan seperti (i, ++i, 1)tidak akan memanggil UB, karena orang mungkin berpikir secara tidak sengaja.

Sourav Ghosh
sumber
+1 Sourav, karena ini menjelaskan mengapa intialisasi ijelas tidak berpengaruh! Namun, saya tidak berpikir itu sangat jelas bagi seorang pria yang tidak mengenal operator koma (dan saya tidak tahu bagaimana mencari bantuan, selain mengajukan pertanyaan). Sayang sekali saya mendapat banyak downvotes! Saya akan memeriksa jawaban lain dan kemudian memutuskan mana yang akan diterima. Terima kasih! Jawaban bagus btw.
gsamaras
Saya merasa saya harus menjelaskan mengapa saya menerima jawaban haccks. Saya siap menerima pertanyaan Anda, karena itu benar-benar menjawab kedua pertanyaan saya. Namun, jika Anda memeriksa komentar dari pertanyaan saya, Anda akan melihat bahwa beberapa orang tidak dapat melihat pada pandangan pertama mengapa ini tidak meminta UB. jawaban haccks memberikan beberapa info yang relevan. Tentu saja, saya punya jawaban tentang UB yang terkait dalam pertanyaan saya, tetapi beberapa orang mungkin melewatkannya. Semoga Anda setuju dengan keinginan saya, jika tidak beri tahu saya. :)
gsamaras
30
i = (i, ++i, 1) + 1;

Mari kita menganalisa langkah demi langkah.

(i,   // is evaluated but ignored, there are other expressions after comma
++i,  // i is updated but the resulting value is ignored too
1)    // this value is finally used
+ 1   // 1 is added to the previous value 1

Jadi kita memperoleh 2. Dan tugas terakhir sekarang:

i = 2;

Apa pun yang ada di saya sebelum ditimpa sekarang.

dlask
sumber
Akan menyenangkan untuk menyatakan bahwa ini terjadi karena operator koma. +1 untuk analisis langkah demi langkah! Jawaban bagus btw.
gsamaras
Saya minta maaf untuk penjelasan yang tidak memadai, saya hanya punya catatan di sana ( ... tapi diabaikan, ada ... ). Saya ingin menjelaskan terutama mengapa ++itidak berkontribusi pada hasil.
dlask
sekarang untuk loop saya akan selalu sepertiint i = 0; for( ;(++i, i<max); )
CoffeDeveloper
19

Hasil dari

(i, ++i, 1)

adalah

1

Untuk

(i,++i,1) 

evaluasi terjadi sedemikian rupa sehingga ,operator membuang nilai yang dievaluasi dan akan mempertahankan nilai yang paling tepat1

Begitu

i = 1 + 1 = 2
Gopi
sumber
1
Ya saya juga memikirkan itu, tapi saya tidak tahu kenapa!
gsamaras
@gsamaras karena operator koma mengevaluasi istilah sebelumnya tetapi membuangnya (mis. tidak menggunakannya untuk penugasan atau sejenisnya)
Marco A.
14

Anda akan menemukan bacaan yang bagus di halaman wiki untuk operator Comma .

Pada dasarnya itu

... mengevaluasi operan pertamanya dan membuang hasilnya, lalu mengevaluasi operan kedua dan mengembalikan nilai ini (dan jenis).

Ini artinya

(i, i++, 1)

pada gilirannya akan mengevaluasi i, membuang hasil, mengevaluasi i++, membuang hasil, dan kemudian mengevaluasi dan kembali 1.

Tomas Aschan
sumber
O_O sih, apakah sintaks itu valid dalam C ++, saya ingat saya punya beberapa tempat di mana saya membutuhkan sintaks itu (pada dasarnya saya menulis: (void)exp; a= exp2;sementara saya hanya perlu a = exp, exp2;)
CoffeDeveloper
13

Anda perlu tahu apa yang dilakukan operator koma di sini:

Ekspresi Anda:

(i, ++i, 1)

Ekspresi pertama,, idievaluasi, ekspresi kedua ++i,, dievaluasi, dan ekspresi ketiga 1, dikembalikan untuk seluruh ekspresi.

Jadi hasilnya adalah: i = 1 + 1.

Untuk pertanyaan bonus Anda, seperti yang Anda lihat, ekspresi pertama itidak berpengaruh sama sekali, jadi kompiler mengeluh.

songyuanyao
sumber
5

Koma memiliki prioritas 'terbalik'. Ini adalah apa yang akan Anda dapatkan dari buku-buku lama dan manual C dari IBM (70-an / 80-an). Jadi 'perintah' terakhir adalah apa yang digunakan dalam ekspresi orangtua.

Di C modern penggunaannya aneh tapi sangat menarik di C lama (ANSI):

do { 
    /* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);

Sementara semua operasi (fungsi) dipanggil dari kiri ke kanan, hanya ekspresi terakhir yang akan digunakan sebagai hasil untuk 'sementara' bersyarat. Ini mencegah penanganan 'goto's untuk menjaga blok unik dari perintah untuk dijalankan sebelum memeriksa kondisi.

EDIT: Ini menghindari juga panggilan ke fungsi penanganan yang bisa menangani semua logika di operan kiri dan mengembalikan hasil logis. Ingat bahwa, kami tidak memiliki fungsi sebaris di masa lalu C. Jadi, ini bisa menghindari panggilan overhead.

Luciano
sumber
Luciano, Anda juga mendapat tautan ke jawaban ini: stackoverflow.com/questions/17902992/… .
gsamaras
Di awal 90-an sebelum fungsi inline, saya sering menggunakannya untuk mengoptimalkan dan menjaga kode tetap terorganisir.
Luciano