Mengapa a +++++ b tidak berfungsi?

89
int main ()
{
   int a = 5,b = 2;
   printf("%d",a+++++b);
   return 0;
}

Kode ini memberikan kesalahan berikut:

error: lvalue diperlukan sebagai operan increment

Tetapi jika saya meletakkan spasi di sepanjang a++ +dan ++b, maka itu berfungsi dengan baik.

int main ()
{
   int a = 5,b = 2;
   printf("%d",a++ + ++b);
   return 0;
}

Apa arti kesalahan pada contoh pertama?

Barshan Das
sumber
3
Mengejutkan setelah sekian lama tidak ada yang menemukan bahwa ekspresi persis yang Anda tanyakan digunakan sebagai contoh dalam standar C99 dan C11. Ini memberikan penjelasan yang bagus juga. Saya telah memasukkan itu dalam jawaban saya.
Shafik Yaghmour
@ShafikYaghmour - Itulah 'Contoh 2' di C11 §6.4 Lexical Elements ¶6 . Ia mengatakan "Fragmen program x+++++ydiuraikan sebagai x ++ ++ + y, yang melanggar batasan pada operator kenaikan, meskipun parse x ++ + ++ ymungkin menghasilkan ekspresi yang benar."
Jonathan Leffler

Jawaban:

98

printf("%d",a+++++b);ditafsirkan (a++)++ + bsesuai dengan Aturan Makan Maksimal ! .

++(postfix) tidak mengevaluasi ke lvaluetetapi membutuhkan operannya menjadi lvalue.

! 6.4 / 4 mengatakan token preprocessing berikutnya adalah urutan karakter terpanjang yang dapat membentuk token preprocessing "

Prasoon Saurav
sumber
181

Penyusun ditulis secara bertahap. Tahap pertama disebut lexer dan mengubah karakter menjadi struktur simbolik. Jadi "++" menjadi sesuatu seperti enum SYMBOL_PLUSPLUS. Kemudian, tahap pengurai mengubahnya menjadi pohon sintaksis abstrak, tetapi tidak dapat mengubah simbol. Anda dapat mempengaruhi lexer dengan menyisipkan spasi (yang mengakhiri simbol kecuali dalam tanda kutip).

Lexer normal adalah greedy (dengan beberapa pengecualian), jadi kode Anda diinterpretasikan sebagai

a++ ++ +b

Input ke parser adalah aliran simbol, jadi kode Anda akan menjadi seperti ini:

[ SYMBOL_NAME(name = "a"), 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS, 
  SYMBOL_NAME(name = "b") 
]

Yang menurut parser salah secara sintaksis. (EDIT berdasarkan komentar: Salah semantik karena Anda tidak dapat menerapkan ++ ke nilai r, yang menghasilkan ++)

a+++b 

adalah

a++ +b

Tidak apa-apa. Begitu juga contoh Anda yang lain.

Lou Franco
sumber
27
+1 Penjelasan yang bagus. Saya harus memilih sendiri: Ini benar secara sintaksis, hanya memiliki kesalahan semantik (upaya untuk menaikkan nilai l yang dihasilkan dari a++).
7
a++menghasilkan nilai r.
Femaref
9
Dalam konteks lexers, algoritma 'greedy' biasanya disebut Maximal Munch ( en.wikipedia.org/wiki/Maximal_munch ).
JoeG
14
Bagus. Banyak bahasa memiliki kasus sudut aneh yang serupa berkat lexing serakah. Inilah yang sangat aneh di mana membuat ekspresi lebih panjang membuatnya lebih baik: Di VBScript x = 10&987&&654&&321adalah ilegal, tetapi cukup aneh x = 10&987&&654&&&321adalah legal.
Eric Lippert
1
Itu tidak ada hubungannya dengan keserakahan dan semua berkaitan dengan keteraturan dan prioritas. ++ lebih tinggi dari + jadi dua ++ akan dikerjakan terlebih dahulu. +++++ b juga akan menjadi + ++ ++ b dan bukan ++ ++ + b. Kredit ke @MByD untuk tautannya.
30

Lexer menggunakan apa yang umumnya disebut algoritma "munch maksimum" untuk membuat token. Itu berarti saat membaca karakter, ia terus membaca karakter sampai menemukan sesuatu yang tidak bisa menjadi bagian dari token yang sama seperti yang sudah dimilikinya (misalnya, jika telah membaca digit jadi yang dimilikinya adalah angka, jika bertemu an A, ia tahu itu tidak bisa menjadi bagian dari nomor tersebut, jadi ia berhenti dan meninggalkan Adi buffer input untuk digunakan sebagai awal dari token berikutnya). Ia kemudian mengembalikan token itu ke parser.

Dalam hal ini, artinya +++++mendapat lexed sebagai a ++ ++ + b. Karena kenaikan pasca pertama menghasilkan nilai r, yang kedua tidak dapat diterapkan padanya, dan kompilator memberikan kesalahan.

Hanya FWIW, di C ++ Anda dapat membebani operator++untuk menghasilkan nilai l, yang memungkinkan ini berfungsi. Sebagai contoh:

struct bad_code { 
    bad_code &operator++(int) { 
        return *this;
    }
    int operator+(bad_code const &other) { 
        return 1;
    }
};

int main() { 
    bad_code a, b;

    int c = a+++++b;
    return 0;
}

Kompilasi dan berjalan (meskipun tidak melakukan apa-apa) dengan kompiler C ++ yang saya miliki (VC ++, g ++, Comeau).

Jerry Coffin
sumber
1
"misalnya, jika sudah membaca angka jadi apa itu adalah angka, jika menemukan A, ia tahu bahwa tidak dapat menjadi bagian dari nomor" 16FAadalah heksadesimal baik-baik saja jumlah yang berisi A.
orlp
1
@ nightcracker: ya, tetapi tanpa 0xdi awal itu akan tetap memperlakukan itu sebagai 16diikuti oleh FA, bukan satu angka heksadesimal.
Jerry Coffin
@ Jerry Coffin: Anda tidak mengatakan 0xbukan bagian dari nomor tersebut.
orlp
@ nightcracker: tidak, saya tidak - mengingat kebanyakan orang tidak menganggap xsatu digit, sepertinya itu tidak perlu.
Jerry Coffin
14

Contoh persis ini tercakup dalam draf standar C99 ( detail yang sama di C11 ) bagian 6.4 Elemen leksikal paragraf 4 yang berbunyi:

Jika aliran masukan telah diurai menjadi token praproses hingga karakter tertentu, token praproses berikutnya adalah urutan karakter terpanjang yang dapat membentuk token praproses. [...]

yang juga dikenal sebagai aturan munch maksimal yang digunakan dalam analisis leksikal untuk menghindari ambiguitas dan bekerja dengan mengambil sebanyak mungkin elemen untuk membentuk token yang valid.

Paragraf ini juga memiliki dua contoh, yang kedua adalah pencocokan tepat untuk pertanyaan Anda dan adalah sebagai berikut:

CONTOH 2 Fragmen program x +++++ y diurai sebagai x ++ ++ + y, yang melanggar batasan pada operator increment, meskipun parse x ++ + ++ y mungkin menghasilkan ekspresi yang benar.

yang memberitahu kita bahwa:

a+++++b

akan diuraikan sebagai:

a ++ ++ + b

yang melanggar batasan kenaikan posting karena hasil kenaikan posting pertama adalah nilai r dan kenaikan posting membutuhkan nilai l. Ini tercakup dalam bagian 6.5.2.4 operator kenaikan dan penurunan Postfix yang mengatakan ( penekanan milik saya ):

Operand operator kenaikan atau penurunan postfix harus memiliki tipe penunjuk atau penunjuk yang memenuhi syarat atau tidak memenuhi syarat dan harus menjadi nilai l yang dapat dimodifikasi.

dan

Hasil dari operator postfix ++ adalah nilai operannya.

Buku C ++ Gotchas juga membahas kasus ini di Gotcha #17 Maximal Munch Problems , masalah yang sama di C ++ juga dan juga memberikan beberapa contoh. Ini menjelaskan bahwa ketika berhadapan dengan kumpulan karakter berikut:

->*

penganalisis leksikal dapat melakukan salah satu dari tiga hal berikut:

  • Memperlakukannya sebagai tiga token: -, >dan*
  • Perlakukan itu sebagai dua token: ->dan*
  • Perlakukan itu sebagai satu token: ->*

Aturan makan maksimal memungkinkannya menghindari ambiguitas ini. Penulis menunjukkan bahwa itu ( dalam konteks C ++ ):

memecahkan lebih banyak masalah daripada yang ditimbulkannya, tetapi dalam dua situasi umum, ini mengganggu.

Contoh pertama adalah templat yang argumen templatnya juga templat ( yang diselesaikan dalam C ++ 11 ), misalnya:

list<vector<string>> lovos; // error!
                  ^^

Yang mengartikan tanda kurung sudut penutup sebagai operator shift , dan oleh karena itu diperlukan ruang untuk menghilangkan ambiguitas:

list< vector<string> > lovos;
                    ^

Kasus kedua melibatkan argumen default untuk pointer, misalnya:

void process( const char *= 0 ); // error!
                         ^^

akan diartikan sebagai *=operator penugasan, solusi dalam hal ini adalah memberi nama parameter dalam deklarasi.

Shafik Yaghmour
sumber
Tahukah Anda bagian mana dari C ++ 11 yang menyatakan aturan mengunyah maksimum? 2.2.3, 2.5.3 menarik, tetapi tidak sejelas C. >>Aturan ditanyakan di: stackoverflow.com/questions/15785496/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 lihat jawaban ini di sini
Shafik Yaghmour
Terima kasih, ini salah satu bagian yang saya tunjuk. Saya akan upvote Anda besok ketika topi saya habis ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件
12

Kompilator Anda berusaha keras untuk mengurai a+++++b, dan menafsirkannya sebagai (a++)++ +b. Sekarang, hasil dari kenaikan pasca ( a++) bukanlah nilai l , yaitu tidak dapat ditambahkan setelah kenaikan lagi.

Harap jangan pernah menulis kode seperti itu dalam program kualitas produksi. Pikirkan tentang orang malang yang mengejar Anda yang perlu menafsirkan kode Anda.

Péter Török
sumber
10
(a++)++ +b

a ++ mengembalikan nilai sebelumnya, rvalue. Anda tidak dapat menaikkan ini.

Erik
sumber
7

Karena itu menyebabkan perilaku tidak terdefinisi.

Yang mana?

c = (a++)++ + b
c = (a) + ++(++b)
c = (a++) + (++b)

Ya, baik Anda maupun kompiler tidak mengetahuinya.

EDIT:

Alasan sebenarnya adalah seperti yang dikatakan oleh yang lain:

Itu ditafsirkan sebagai (a++)++ + b.

tapi kenaikan pos membutuhkan lvalue (yang merupakan variabel dengan nama) tetapi (a ++) mengembalikan rvalue yang tidak bisa dinaikkan sehingga mengarah ke pesan kesalahan yang Anda dapatkan.

Terima kasih kepada yang lain untuk menunjukkan hal ini.

RedX
sumber
5
Anda bisa mengatakan hal yang sama untuk a +++ b - (a ++) + b dan a + (++ b) memiliki hasil yang berbeda.
Michael Chinen
4
sebenarnya, postfix ++ memiliki prioritas lebih tinggi daripada awalan ++, begitu a+++bjuga selalua++ + b
MByD
4
Saya tidak berpikir ini adalah jawaban yang benar, tetapi saya bisa saja salah. Saya pikir lexer mendefinisikannya menjadi a++ ++ +byang tidak dapat diuraikan.
Lou Franco
2
Saya tidak setuju dengan jawaban ini. 'perilaku tidak terdefinisi' sangat berbeda dari ambiguitas tokenisasi; dan menurut saya masalahnya juga bukan.
Jim Blackler
2
"Jika tidak, a +++++ b akan mengevaluasi ke ((a ++) ++) + b" ... pandangan saya saat ini adalah a+++++b tidak mengevaluasi ke (a++)++)+b. Tentunya dengan GCC jika Anda memasukkan tanda kurung tersebut dan membangun kembali, pesan kesalahan tidak berubah.
Jim Blackler
5

Saya pikir kompilator melihatnya sebagai

c = ((a ++) ++) + b

++harus memiliki nilai yang dapat dimodifikasi sebagai operan. a adalah nilai yang dapat dimodifikasi. a++namun merupakan 'rvalue', tidak dapat dimodifikasi.

By the way kesalahan saya lihat di GCC C adalah sama, tetapi berbeda-worded: lvalue required as increment operand.

Jim Blackler
sumber
0

Ikuti urutan presesi ini

1. ++ (kenaikan sebelum)

2. + - (penambahan atau pengurangan)

3. "x" + "y" menambahkan kedua urutan tersebut

int a = 5,b = 2; printf("%d",a++ + ++b); //a is 5 since it is post increment b is 3 pre increment return 0; //it is 5+3=8

rakshit ks
sumber