Mengapa konstruksi ini menggunakan perilaku tidak terdefinisi sebelum dan sesudah kenaikan?

Jun 04 2009
#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?
}

Jawaban

573 unwind Jun 04 2009 at 16:20

C memiliki konsep perilaku tidak terdefinisi, yaitu beberapa konstruksi bahasa valid secara sintaksis tetapi Anda tidak dapat memprediksi perilaku saat kode dijalankan.

Sejauh yang saya tahu, standar tersebut tidak secara eksplisit mengatakan mengapa konsep perilaku tidak terdefinisi ada. Dalam pikiran saya, itu hanya karena perancang bahasa ingin ada kelonggaran dalam semantik, daripada mengharuskan semua implementasi menangani luapan bilangan bulat dengan cara yang persis sama, yang kemungkinan besar akan membebankan biaya kinerja yang serius, mereka hanya meninggalkan perilakunya undefined sehingga jika Anda menulis kode yang menyebabkan integer overflow, apa pun bisa terjadi.

Jadi, dengan pemikiran tersebut, mengapa "masalah" ini? Bahasa dengan jelas mengatakan bahwa hal-hal tertentu mengarah pada perilaku yang tidak terdefinisi . Tidak ada masalah, tidak ada yang "harus" terlibat. Jika perilaku tidak terdefinisi berubah ketika salah satu variabel yang terlibat dideklarasikan volatile, itu tidak membuktikan atau mengubah apapun. Itu tidak ditentukan ; Anda tidak dapat bernalar tentang perilaku tersebut.

Contoh Anda yang paling menarik, yang dengan

u = (u++);

adalah contoh buku teks tentang perilaku tidak terdefinisi (lihat entri Wikipedia tentang titik urutan ).

76 badp May 24 2010 at 20:26

Cukup 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 pikirkan sedang terjadi:

$ cat evil.c void evil(){ int i = 0; i+= i++ + ++i; } $ gcc evil.c -c -o evil.bin
$ gdb evil.bin (gdb) disassemble evil Dump of assembler code for function evil: 0x00000000 <+0>: push %ebp 0x00000001 <+1>: mov %esp,%ebp 0x00000003 <+3>: sub $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp) // i = 0 i = 0 0x0000000d <+13>: addl $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(Saya ... seandainya instruksi 0x00000014 adalah semacam pengoptimalan kompiler?)

66 Christoph Jun 04 2009 at 16:35

Saya pikir bagian yang relevan dari standar C99 adalah 6,5 Ekspresi, §2

Antara titik urutan sebelumnya dan berikutnya, nilai simpanan objek harus dimodifikasi paling banyak satu kali dengan evaluasi ekspresi. Selanjutnya nilai prior harus dibaca hanya untuk menentukan nilai yang akan disimpan.

dan 6.5.16 Operator penugasan, §4:

Urutan evaluasi operand tidak ditentukan. Jika dilakukan upaya untuk mengubah hasil dari operator penetapan atau untuk mengaksesnya setelah titik urutan berikutnya, perilaku tidak ditentukan.

61 haccks Jun 27 2015 at 07:27

Sebagian besar jawaban di sini dikutip dari standar C menekankan bahwa perilaku konstruksi ini tidak terdefinisi. Untuk memahami mengapa perilaku konstruksi ini tidak ditentukan , mari kita pahami istilah-istilah ini terlebih dahulu berdasarkan standar C11:

Diurutkan: (5.1.2.3)

Diberikan dua evaluasi Adan B, jika Adiurutkan sebelumnya B, maka eksekusi Aharus mendahului eksekusi B.

Tidak berurutan:

Jika Atidak diurutkan sebelum atau sesudah B, maka Adan Btidak diurutkan.

Evaluasi bisa menjadi salah satu dari dua hal:

  • perhitungan nilai , yang mengerjakan hasil ekspresi; dan
  • efek samping , yaitu modifikasi objek.

Titik Urutan:

Kehadiran titik urutan antara evaluasi ekspresi Adan Bmenyiratkan bahwa setiap perhitungan nilai dan efek samping yang terkait dengan Adiurutkan sebelum setiap perhitungan nilai dan efek samping yang terkait dengannya B.

Sekarang datang ke pertanyaan, untuk ekspresi seperti

int i = 1;
i = i++;

standar mengatakan bahwa:

6.5 Ekspresi:

Jika efek samping pada objek skalar adalah unsequenced relatif baik efek samping yang berbeda pada objek skalar yang sama atau perhitungan nilai menggunakan nilai dari objek skalar yang sama, perilaku tidak terdefinisi . [...]

Oleh karena itu, ekspresi di atas memanggil UB karena dua efek samping pada objek yang sama itidak diurutkan relatif satu sama lain. Artinya tidak diurutkan apakah efek samping oleh tugas iakan dilakukan sebelum atau sesudah efek samping oleh ++.
Bergantung pada apakah penugasan dilakukan sebelum atau setelah kenaikan, hasil yang berbeda akan dihasilkan dan itulah salah satu kasus perilaku yang tidak ditentukan .

Mari ganti nama idi kiri tugas menjadi ildan di kanan tugas (dalam ekspresi i++) menjadi ir, lalu ekspresi menjadi seperti

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Hal penting tentang ++operator Postfix adalah:

hanya karena ++datang setelah variabel tidak berarti bahwa kenaikan terjadi terlambat . Kenaikan bisa terjadi sedini kompiler suka selama kompilator memastikan bahwa nilai aslinya digunakan .

Artinya, ekspresi il = ir++dapat dievaluasi sebagai

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

atau

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

menghasilkan dua hasil yang berbeda 1dan 2yang bergantung pada urutan efek samping berdasarkan tugas ++dan karenanya memanggil UB.

54 ShafikYaghmour Aug 16 2013 at 02:25

Perilaku tersebut tidak dapat benar-benar dijelaskan karena memicu perilaku 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 membuatnya baik menebak dalam kasus yang sangat spesifik dengan kompiler dan lingkungan tertentu, tetapi tolong jangan lakukan itu di dekat produksi.

Jadi beralih ke perilaku yang tidak ditentukan , dalam draf c99 bagian standar6.5 paragraf 3 mengatakan ( penekanan dari saya ):

Pengelompokan operator dan operand ditunjukkan oleh sintaks.74) Kecuali ditentukan kemudian (untuk function-call (), &&, ||,?:, Dan operator koma), urutan evaluasi subekspresi dan urutan dalam efek samping mana yang terjadi keduanya tidak ditentukan.

Jadi ketika kita memiliki garis seperti ini:

i = i++ + ++i;

kita tidak tahu apakah i++atau ++iakan dievaluasi pertama. Ini terutama untuk memberikan opsi pengoptimalan yang lebih baik kepada kompiler .

Kami juga memiliki perilaku undefined sini juga karena program ini memodifikasi variabel ( i, u, dll ..) lebih dari sekali antara urutan poin . Dari draf bagian standar 6.5paragraf 2 ( penekanan dari saya ):

Antara titik urutan sebelumnya dan berikutnya, nilai simpanan objek harus dimodifikasi paling banyak satu kali dengan evaluasi ekspresi. Selanjutnya nilai prior harus dibaca hanya untuk menentukan nilai yang akan disimpan .

itu mengutip contoh kode berikut sebagai tidak terdefinisi:

i = ++i + 1;
a[i++] = i; 

Dalam semua contoh ini, kode mencoba mengubah objek lebih dari sekali dalam titik urutan yang sama, yang akan diakhiri dengan ;di masing-masing kasus berikut:

i = i++ + ++i;
^   ^       ^

i = (i++);
^    ^

u = u++ + ++u;
^   ^       ^

u = (u++);
^    ^

v = v++ + ++v;
^   ^       ^

Perilaku tidak ditentukan didefinisikan dalam draf standar c99 di bagian 3.4.4sebagai:

penggunaan nilai yang tidak ditentukan, atau perilaku lain di mana Standar Internasional ini memberikan dua kemungkinan atau lebih dan tidak memberlakukan persyaratan lebih lanjut yang dipilih dalam hal apa pun

dan perilaku tidak terdefinisi didefinisikan di bagian 3.4.3sebagai:

perilaku, setelah menggunakan konstruksi program yang tidak dapat dibawa atau salah atau data yang salah, yang mana Standar ini tidak memberlakukan persyaratan

dan mencatat bahwa:

Perilaku yang mungkin tidak terdefinisi berkisar dari mengabaikan situasi sepenuhnya dengan hasil yang tidak dapat diprediksi, berperilaku selama penerjemahan atau pelaksanaan program dengan cara yang terdokumentasi, karakteristik lingkungan (dengan atau tanpa penerbitan pesan diagnostik), untuk menghentikan terjemahan atau eksekusi (dengan penerbitan dari pesan diagnostik).

38 SteveSummit Jun 18 2015 at 18:55

Cara lain untuk menjawab ini, daripada terjebak dalam detail misterius dari titik-titik urutan dan perilaku yang tidak terdefinisi, adalah dengan bertanya, apa maksudnya? Apa yang programmer coba lakukan?

Fragmen pertama yang ditanya tentang,, i = i++ + ++icukup jelas tidak masuk akal dalam buku saya. Tidak ada yang akan pernah menulisnya dalam program nyata, tidak jelas apa yang dilakukannya, tidak ada algoritma yang dapat dibayangkan seseorang dapat mencoba membuat kode yang akan menghasilkan urutan operasi yang dibuat-buat khusus ini. Dan karena tidak jelas bagi Anda dan saya apa yang harus dilakukannya, tidak masalah dalam buku saya jika penyusun juga tidak tahu apa yang harus dilakukannya.

Fragmen kedua,, i = i++sedikit lebih mudah dipahami. Seseorang dengan jelas mencoba menaikkan i, dan mengembalikan hasilnya ke i. Tetapi ada beberapa cara untuk melakukan ini di C. Cara paling dasar untuk menambahkan 1 ke i, dan menetapkan hasilnya kembali ke i, adalah sama di hampir semua bahasa pemrograman:

i = i + 1

C, tentu saja, memiliki pintasan praktis:

i++

Ini berarti, "tambahkan 1 ke i, dan tetapkan hasilnya kembali ke i". Jadi jika kita membuat campuran keduanya, dengan menulis

i = i++

apa yang sebenarnya kami katakan adalah "tambahkan 1 ke i, dan tetapkan hasilnya kembali ke i, dan tetapkan hasilnya kembali ke i". Kami bingung, jadi tidak terlalu mengganggu saya jika kompiler menjadi bingung juga.

Secara realistis, satu-satunya saat ekspresi gila ini ditulis adalah ketika orang menggunakannya sebagai contoh buatan tentang bagaimana ++ seharusnya bekerja. Dan tentu saja penting untuk memahami cara kerja ++. Tapi satu aturan praktis untuk menggunakan ++ adalah, "Jika tidak jelas apa arti ekspresi menggunakan ++, jangan menulisnya."

Kami biasa menghabiskan waktu berjam-jam di comp.lang.c mendiskusikan ekspresi seperti ini dan mengapa tidak terdefinisi. Dua dari jawaban saya yang lebih panjang, yang mencoba menjelaskan alasannya, diarsipkan di web:

  • Mengapa Standar tidak menjelaskan apa yang dilakukannya?
  • Bukankah prioritas operator menentukan urutan evaluasi?

Lihat juga pertanyaan 3.8 dan pertanyaan lainnya di bagian 3 dari daftar C FAQ .

27 P.P Dec 31 2015 at 03:26

Seringkali pertanyaan ini dikaitkan sebagai duplikat pertanyaan yang terkait dengan kode suka

printf("%d %d\n", i, i++);

atau

printf("%d %d\n", ++i, i++);

atau varian serupa.

Meskipun ini juga merupakan perilaku yang tidak terdefinisi seperti yang telah disebutkan, ada perbedaan halus printf()saat membandingkan dengan pernyataan seperti:

x = i++ + i++;

Dalam pernyataan berikut:

printf("%d %d\n", ++i, i++);

yang urutan evaluasi dari argumen dalam printf()adalah tidak ditentukan . Artinya, ekspresi i++dan ++idapat dievaluasi dalam urutan apa pun. Standar C11 memiliki beberapa deskripsi yang relevan tentang ini:

Lampiran J, perilaku tidak ditentukan

Urutan di mana penunjuk fungsi, argumen, dan subekspresi dalam argumen dievaluasi dalam panggilan fungsi (6.5.2.2).

3.4.4, perilaku tidak ditentukan

Penggunaan nilai yang tidak ditentukan, atau perilaku lain di mana Standar Internasional ini memberikan dua kemungkinan atau lebih dan tidak memberlakukan persyaratan lebih lanjut yang dipilih dalam hal apa pun.

CONTOH Contoh perilaku tidak ditentukan adalah urutan argumen ke fungsi dievaluasi.

The perilaku yang tidak ditentukan sendiri tidak masalah. Pertimbangkan contoh ini:

printf("%d %d\n", ++x, y++);

Ini juga memiliki perilaku yang tidak ditentukan karena urutan evaluasi ++xdan y++tidak ditentukan. Tapi itu pernyataan yang sah dan valid. Tidak ada perilaku yang tidak ditentukan dalam pernyataan ini. Karena modifikasi ( ++xdan y++) dilakukan pada objek yang berbeda .

Apa yang membuat pernyataan berikut

printf("%d %d\n", ++i, i++);

karena perilaku tidak terdefinisi adalah kenyataan bahwa kedua ekspresi ini memodifikasi objek yang samai tanpa titik urutan yang mengganggu .


Detail lainnya adalah bahwa koma yang terlibat dalam panggilan printf () adalah pemisah , bukan operator koma .

Ini merupakan perbedaan penting karena operator koma memasukkan titik urutan antara evaluasi operand mereka, yang membuat hal berikut ini legal:

int i = 5;
int j;

j = (++i, i++);  // No undefined behaviour here because the comma operator 
                 // introduces a sequence point between '++i' and 'i++'

printf("i=%d j=%d\n",i, j); // prints: i=7 j=6

Operator koma mengevaluasi operannya dari kiri ke kanan dan hanya menghasilkan nilai dari operan terakhir. Jadi j = (++i, i++);, ++ikenaikan iuntuk 6dan i++hasil nilai lama i( 6) yang ditugaskan untuk j. Kemudian imenjadi 7karena kenaikan pasca.

Jadi jika koma dalam pemanggilan fungsi menjadi operator koma maka

printf("%d %d\n", ++i, i++);

tidak akan menjadi masalah. Tapi itu memanggil perilaku tidak terdefinisi karena koma di sini adalah pemisah .


Bagi mereka yang baru mengenal perilaku tidak terdefinisi akan mendapat manfaat dari membaca Apa yang Harus Diketahui Setiap Programmer C Tentang Perilaku Tak Terdefinisi untuk memahami konsep dan banyak varian lain dari perilaku tak terdefinisi di C.

Postingan ini: Perilaku tidak terdefinisi, tidak ditentukan, dan terdefinisi implementasi juga relevan.

23 supercat Dec 06 2012 at 01:30

Meskipun kecil kemungkinannya ada kompiler dan pemroses yang benar-benar melakukannya, akan legal, di bawah standar C, bagi compiler untuk mengimplementasikan "i ++" dengan urutan:

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

Meskipun menurut saya tidak 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-utas lebih mudah (misalnya, akan menjamin bahwa jika dua utas mencoba melakukan hal di atas urutan secara bersamaan, iakan bertambah dua) dan tidak sepenuhnya tidak dapat dibayangkan bahwa beberapa prosesor masa depan mungkin menyediakan fitur seperti itu.

Jika penyusun akan menulis i++seperti yang ditunjukkan di atas (legal di bawah standar) dan menyelingi instruksi di atas selama evaluasi ekspresi keseluruhan (juga legal), dan jika tidak kebetulan memperhatikan bahwa salah satu instruksi lain terjadi untuk mengakses i, adalah mungkin (dan legal) bagi kompiler untuk menghasilkan urutan instruksi yang akan menemui jalan buntu. Yang pasti, kompilator hampir pasti akan mendeteksi masalah dalam kasus di mana variabel yang sama idigunakan di kedua tempat, tetapi jika rutin menerima referensi ke dua pointer pdan q, dan menggunakan (*p)dan (*q)dalam ekspresi di atas (daripada menggunakan idua kali) compiler tidak akan diminta untuk mengenali atau menghindari kebuntuan yang akan terjadi jika alamat objek yang sama diteruskan untuk keduanya pdan q.

18 AnttiHaapala Mar 26 2017 at 21:58

Sementara sintaks ekspresi like a = a++or a++ + a++legal, perilaku konstruksi ini tidak terdefinisi karena harus dalam standar C tidak ditaati. C99 6.5p2 :

  1. Antara titik urutan sebelumnya dan berikutnya, nilai simpanan objek harus dimodifikasi paling banyak satu kali dengan evaluasi ekspresi. [72] Selanjutnya, nilai sebelumnya harus dibaca hanya untuk menentukan nilai yang akan disimpan [73]

Dengan catatan kaki 73 lebih memperjelas hal itu

  1. Paragraf ini membuat ekspresi pernyataan yang tidak terdefinisi seperti

    i = ++i + 1;
    a[i++] = i;
    

    sambil mengizinkan

    i = i + 1;
    a[i] = i;
    

Berbagai titik urutan tercantum dalam Lampiran C C11 (dan C99 ):

  1. Berikut ini adalah titik-titik urutan yang dijelaskan dalam 5.1.2.3:

    • Antara evaluasi penunjuk fungsi dan argumen aktual dalam panggilan fungsi dan panggilan aktual. (6.5.2.2).
    • Antara evaluasi operan pertama dan kedua dari operator berikut: logika AND && (6.5.13); logis OR || (6.5.14); koma, (6.5.17).
    • Antara evaluasi operan pertama dari kondisional? : operator dan salah satu dari operan kedua dan ketiga yang dievaluasi (6.5.15).
    • Akhir dari deklarator lengkap: deklarator (6.7.6);
    • Antara evaluasi ekspresi penuh dan ekspresi penuh berikutnya yang akan dievaluasi. Berikut ini adalah ekspresi lengkap: penginisialisasi yang bukan bagian dari literal majemuk (6.7.9); ekspresi dalam pernyataan ekspresi (6.8.3); ekspresi pengontrol dari pernyataan pemilihan (if atau switch) (6.8.4); ekspresi pengendali sementara atau melakukan pernyataan (6.8.5); masing-masing ekspresi (opsional) dari a untuk pernyataan (6.8.5.3); ekspresi (opsional) dalam pernyataan kembali (6.8.6.4).
    • Segera sebelum fungsi pustaka kembali (7.1.4).
    • Setelah tindakan yang terkait dengan setiap penentu konversi fungsi masukan / keluaran yang diformat (7.21.6, 7.29.2).
    • Tepat sebelum dan segera setelah setiap panggilan ke fungsi perbandingan, dan juga antara panggilan apa pun ke fungsi perbandingan dan setiap pergerakan objek yang diteruskan sebagai argumen ke panggilan tersebut (7.22.5).

Kata-kata dari paragraf yang sama di C11 adalah:

  1. Jika efek samping pada objek skalar tidak diurutkan secara relatif terhadap efek samping yang berbeda pada objek skalar yang sama atau penghitungan nilai menggunakan nilai objek skalar yang sama, perilaku tidak ditentukan. Jika ada beberapa urutan subekspresi ekspresi yang diperbolehkan, perilakunya tidak ditentukan jika efek samping yang tidak diurutkan tersebut terjadi di salah satu urutan.84)

Anda dapat mendeteksi kesalahan semacam itu dalam program dengan misalnya menggunakan versi terbaru GCC dengan -Walldan -Werror, kemudian GCC akan langsung menolak untuk mengompilasi program Anda. Berikut ini adalah keluaran dari gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function ‘main’:
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

Bagian yang penting adalah mengetahui apa itu sequence point - dan apa itu sequence point dan apa yang bukan . Misalnya operator koma adalah titik urutan, jadi

j = (i ++, ++ i);

didefinisikan dengan baik, dan akan bertambah isatu, menghasilkan nilai lama, membuang nilai itu; kemudian pada operator koma, selesaikan efek sampingnya; dan kemudian bertambah isatu, dan nilai yang dihasilkan menjadi nilai ekspresi - yaitu ini hanyalah cara yang dibuat-buat untuk menulis j = (i += 2)yang sekali lagi merupakan cara "pintar" untuk menulis

i += 2;
j = i;

Namun, ,daftar argumen dalam fungsi bukanlah operator koma, dan tidak ada titik urutan antara evaluasi argumen yang berbeda; sebaliknya evaluasi mereka tidak diurutkan satu sama lain; jadi panggilan fungsi

int i = 0;
printf("%d %d\n", i++, ++i, i);

memiliki perilaku tidak terdefinisi karena tidak ada titik urutan antara evaluasi i++dan ++idalam argumen fungsi , dan nilai dari iitu dimodifikasi dua kali, oleh keduanya i++dan ++i, antara titik urutan sebelumnya dan berikutnya.

14 NikhilVidhani Sep 11 2014 at 19:36

Standar C mengatakan bahwa variabel hanya boleh ditempatkan paling banyak sekali antara dua titik urutan. Titik koma misalnya adalah titik urutan.
Jadi setiap pernyataan dalam bentuk:

i = i++;
i = i++ + ++i;

dan seterusnya melanggar aturan itu. Standar tersebut juga mengatakan bahwa perilaku tidak ditentukan dan bukan tidak ditentukan. Beberapa kompiler mendeteksi ini dan menghasilkan beberapa hasil tetapi ini tidak sesuai standar.

Namun, dua variabel berbeda dapat ditambahkan di antara dua titik urutan.

while(*src++ = *dst++);

Di atas adalah praktik pengkodean umum saat menyalin / menganalisis string.

11 TomOnTime Apr 08 2015 at 10:20

Di https://stackoverflow.com/questions/29505280/incrementing-array-index-in-c seseorang bertanya tentang pernyataan seperti:

int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);

yang mencetak 7 ... OP mengharapkannya mencetak 6.

Penambahan ++itidak dijamin selesai sebelum penghitungan lainnya. Faktanya, penyusun yang berbeda akan mendapatkan hasil yang berbeda di sini. Dalam contoh yang Anda berikan, pertama 2 ++idieksekusi, maka nilai-nilai k[]yang dibaca, maka yang terakhir ++iitu k[].

num = k[i+1]+k[i+2] + k[i+3];
i += 3

Kompiler modern akan mengoptimalkannya dengan sangat baik. Bahkan, mungkin lebih baik daripada kode yang Anda tulis semula (dengan asumsi itu bekerja seperti yang Anda harapkan).

6 SteveSummit Aug 16 2018 at 18:54

Pertanyaan Anda mungkin bukan, "Mengapa konstruksi ini berperilaku tidak terdefinisi di 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.

Jawaban ini 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 bahwa Anda telah mendengar definisi dasar dari C ++dan --operator sekarang, dan bagaimana bentuk awalan ++xberbeda dari bentuk postfix x++. Tetapi operator ini sulit untuk dipikirkan, jadi untuk memastikan Anda mengerti, mungkin Anda menulis program pengujian kecil yang melibatkan sesuatu seperti

int x = 5;
printf("%d %d %d\n", x, ++x, x++);

Namun, yang mengejutkan Anda, program ini tidak membantu Anda memahami - program ini mencetak keluaran yang aneh, tidak 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, kemungkinan besar Anda akan mendapatkan dua jawaban yang berbeda! Ada apa dengan itu? Jawaban mana yang benar? (Dan jawabannya adalah keduanya, atau tidak satu pun dari mereka.)

Seperti yang Anda dengar sekarang, semua ekspresi ini tidak ditentukan , yang berarti bahasa C tidak menjamin apa yang akan mereka lakukan. Ini adalah hasil yang aneh dan mengejutkan, karena Anda mungkin mengira bahwa program apa pun yang dapat Anda tulis, selama program itu dikompilasi dan dijalankan, akan menghasilkan keluaran yang unik dan terdefinisi dengan baik. Namun dalam kasus perilaku tidak terdefinisi, tidak demikian.

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, 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 kembali ke dua contoh yang telah saya gunakan dalam jawaban ini. Saat saya menulis

printf("%d %d %d\n", x, ++x, x++);

pertanyaannya adalah, sebelum memanggil printf, apakah compiler menghitung nilai xterlebih dahulu, atau x++, atau mungkin ++x? Tapi ternyata kami tidak tahu . Tidak ada aturan di C yang mengatakan bahwa argumen ke suatu fungsi dievaluasi dari kiri ke kanan, atau kanan ke kiri, atau dalam urutan lain. Jadi kita tidak bisa mengatakan apakah compiler akan melakukan xpertama, kemudian ++x, kemudian x++, atau x++kemudian ++xkemudian x, atau beberapa urutan lainnya. Tetapi urutannya jelas penting, karena tergantung pada urutan mana yang digunakan penyusun, kita akan mendapatkan hasil cetakan yang berbeda dengan jelas printf.

Bagaimana dengan ekspresi gila ini?

x = x++ + ++x;

Masalah dengan ekspresi ini adalah ekspresi ini berisi tiga upaya berbeda untuk mengubah nilai x: (1) x++bagian mencoba menambahkan 1 ke x, menyimpan nilai baru x, dan mengembalikan nilai lama x; (2) ++xbagian 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 itu yang akan "menang"? Manakah dari tiga nilai yang benar-benar akan diberikan x? Sekali lagi, dan mungkin yang mengejutkan, tidak ada aturan di C yang harus diberitahukan kepada kita.

Anda mungkin membayangkan bahwa presedensi atau asosiatif atau evaluasi kiri-ke-kanan memberi tahu Anda urutan kejadian, tetapi sebenarnya tidak. Anda mungkin tidak mempercayai saya, tapi mohon percaya kata-kata saya, dan saya akan mengatakannya lagi: prioritas dan asosiatif tidak menentukan setiap aspek urutan evaluasi suatu ekspresi dalam C. Secara khusus, jika dalam satu ekspresi ada beberapa tempat berbeda di mana kita mencoba untuk menetapkan nilai baru pada sesuatu seperti x, prioritas dan asosiatif tidak memberi tahu kita upaya mana yang terjadi pertama, atau terakhir, atau apa pun.


Jadi dengan semua latar belakang dan pendahuluan itu, jika Anda ingin memastikan bahwa semua program Anda didefinisikan dengan baik, ekspresi mana yang dapat Anda tulis, dan mana yang tidak dapat Anda tulis?

Semua ekspresi 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 ekspresi ini tidak ditentukan:

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 membedakan ekspresi mana yang terdefinisi dengan baik, dan ekspresi mana yang tidak terdefinisi?

Seperti yang saya katakan sebelumnya, ekspresi tidak terdefinisi adalah ekspresi di mana ada terlalu banyak hal yang terjadi sekaligus, di mana Anda tidak dapat memastikan apa yang terjadi, dan di mana urutan itu penting:

  1. Jika ada satu variabel yang dimodifikasi (ditetapkan ke) di dua atau lebih tempat berbeda, bagaimana Anda tahu modifikasi mana yang terjadi lebih dulu?
  2. Jika ada variabel yang dimodifikasi di satu tempat, dan nilainya digunakan di tempat lain, bagaimana Anda tahu apakah variabel 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 dimodifikasi paling banyak sekali, dan jika variabel dimodifikasi, Anda juga tidak mencoba menggunakan nilai variabel itu di tempat lain.

5 alinsoar Oct 13 2017 at 20:58

Penjelasan yang bagus tentang apa yang terjadi pada jenis komputasi ini disediakan dalam dokumen n1188 dari situs ISO W14 .

Saya menjelaskan idenya.

Aturan utama dari standar ISO 9899 yang berlaku dalam situasi ini adalah 6.5p2.

Antara titik urutan sebelumnya dan berikutnya, nilai simpanan objek harus dimodifikasi paling banyak satu kali dengan evaluasi ekspresi. Selanjutnya nilai prior harus dibaca hanya untuk menentukan nilai yang akan disimpan.

Titik urutan dalam ekspresi seperti i=i++adalah sebelum i=dan sesudah i++.

Pada makalah yang saya kutip di atas dijelaskan bahwa Anda dapat mengetahui program yang dibentuk oleh kotak-kotak kecil, setiap kotak berisi petunjuk antara 2 titik urutan yang berurutan. Titik urutan ditentukan dalam lampiran C standar, jika i=i++ada 2 titik urutan yang membatasi ekspresi penuh. Ekspresi seperti itu secara sintaksis setara dengan entri expression-statementdalam bentuk tata bahasa Backus-Naur (tata bahasa disediakan dalam lampiran A Standar).

Jadi urutan instruksi di dalam kotak tidak memiliki urutan yang jelas.

i=i++

bisa diartikan sebagai

tmp = i
i=i+1
i = tmp

atau sebagai

tmp = i
i = tmp
i=i+1

karena kedua bentuk untuk menafsirkan kode i=i++ini valid dan karena keduanya menghasilkan jawaban yang berbeda, perilakunya tidak ditentukan.

Jadi titik urutan dapat dilihat di awal dan akhir setiap kotak yang menyusun program [kotak adalah unit atom di C] dan di dalam kotak urutan instruksi tidak ditentukan dalam semua kasus. Mengubah urutan itu terkadang dapat mengubah hasilnya.

EDIT:

Sumber lain yang bagus untuk menjelaskan ambiguitas tersebut adalah entri dari situs c-faq (juga diterbitkan sebagai buku ), yaitu di sini dan di sini dan di sini .

3 MohamedEl-Nakib Jun 11 2017 at 05:56

Alasannya adalah program ini menjalankan perilaku yang tidak terdefinisi. Masalahnya terletak pada urutan evaluasi, karena tidak ada titik urutan yang diperlukan menurut standar C ++ 98 (tidak ada operasi yang diurutkan sebelum atau sesudah yang lain menurut terminologi C ++ 11).

Namun jika Anda tetap menggunakan satu kompilator, Anda akan menemukan perilaku persisten, selama Anda tidak menambahkan pemanggilan atau penunjuk fungsi, yang akan membuat perilaku lebih berantakan.

  • Jadi pertama GCC: Menggunakan Nuwen MinGW 15 GCC 7.1 Anda akan mendapatkan:

    #include<stdio.h>
    int main(int argc, char ** argv)
    {
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 2
    
    i = 1;
    i = (i++);
    printf("%d\n", i); //1
    
    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 2
    
    u = 1;
    u = (u++);
    printf("%d\n", u); //1
    
    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); //2
    

    }

Bagaimana cara kerja GCC? itu mengevaluasi sub ekspresi di urutan kiri ke kanan untuk sisi kanan (RHS), kemudian menetapkan nilai ke sisi kiri (LHS). Ini persis seperti bagaimana Java dan C # berperilaku dan mendefinisikan standar mereka. (Ya, perangkat lunak yang setara di Java dan C # telah menentukan perilaku). Ini mengevaluasi setiap sub ekspresi satu per satu dalam Pernyataan RHS dalam urutan kiri ke kanan; untuk setiap sub ekspresi: ++ c (pre-increment) dievaluasi terlebih dahulu kemudian nilai c digunakan untuk operasi, kemudian post increment c ++).

menurut GCC C ++: Operator

Di GCC C ++, prioritas operator mengontrol urutan di mana masing-masing operator dievaluasi

kode yang setara dalam perilaku yang ditentukan C ++ seperti yang dipahami GCC:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

Lalu kita pergi ke Visual Studio . Visual Studio 2015, Anda mendapatkan:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

Bagaimana cara kerja studio visual, dibutuhkan pendekatan lain, ia mengevaluasi semua ekspresi pra-penambahan di lintasan pertama, kemudian menggunakan nilai variabel dalam operasi di lintasan kedua, menetapkan dari RHS ke LHS di lintasan ketiga, lalu pada lintasan terakhir mengevaluasi semua ekspresi kenaikan pasca dalam satu lintasan.

Jadi yang setara dalam perilaku yang ditentukan C ++ sebagai Visual C ++ memahami:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

sebagai dokumentasi Visual Studio menyatakan di Precedence dan Order of Evaluation :

Jika beberapa operator muncul bersama, mereka memiliki prioritas yang sama dan dievaluasi menurut asosiatifnya. Operator dalam tabel dijelaskan di bagian yang dimulai dengan Operator Postfix.