Tại sao a +++++ b không hoạt động?


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

Mã này đưa ra lỗi sau:

error: lvalue bắt buộc như toán hạng tăng dần

Nhưng nếu tôi đặt khoảng trắng trong suốt a++ +++b, thì nó hoạt động tốt.

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

Lỗi trong ví dụ đầu tiên có nghĩa là gì?


3
Thật là ngạc nhiên sau ngần ấy thời gian không ai phát hiện ra rằng biểu thức chính xác mà bạn đang hỏi lại được sử dụng làm ví dụ trong tiêu chuẩn C99 và C11. Nó cũng đưa ra một lời giải thích tốt. Tôi đã bao gồm điều đó trong câu trả lời của tôi.
Shafik Yaghmour

@ShafikYaghmour - Đó là 'Ví dụ 2' trong C11 §6.4 Lexical Elements ¶6 . Nó cho biết "Phân đoạn chương trình x+++++yđược phân tích cú pháp x ++ ++ + y, điều này vi phạm một hạn chế đối với các toán tử gia tăng, mặc dù việc phân tích cú pháp x ++ + ++ ycó thể mang lại một biểu thức đúng."
Jonathan Leffler

Câu trả lời:


97

printf("%d",a+++++b);được hiểu là (a++)++ + btheo Quy tắc Maximal Munch ! .

++(postfix) không đánh giá thành một lvaluenhưng nó yêu cầu toán hạng của nó phải là một lvalue.

! 6.4 / 4 cho biết mã thông báo tiền xử lý tiếp theo là chuỗi ký tự dài nhất có thể tạo thành mã thông báo tiền xử lý "


181

Các trình biên dịch được viết theo từng giai đoạn. Giai đoạn đầu tiên được gọi là lexer và biến các ký tự thành một cấu trúc biểu tượng. Vì vậy, "++" trở thành một cái gì đó giống như một enum SYMBOL_PLUSPLUS. Sau đó, giai đoạn phân tích cú pháp biến nó thành một cây cú pháp trừu tượng, nhưng nó không thể thay đổi các ký hiệu. Bạn có thể ảnh hưởng đến lexer bằng cách chèn dấu cách (các ký hiệu kết thúc trừ khi chúng nằm trong dấu ngoặc kép).

Lexers bình thường rất tham lam (với một số ngoại lệ), vì vậy mã của bạn đang được hiểu là

a++ ++ +b

Đầu vào cho trình phân tích cú pháp là một dòng ký hiệu, vì vậy mã của bạn sẽ giống như sau:

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

Mà trình phân tích cú pháp cho rằng không chính xác về mặt cú pháp. (CHỈNH SỬA dựa trên nhận xét: Không chính xác về mặt ngữ nghĩa vì bạn không thể áp dụng ++ cho giá trị r, kết quả là a ++)

a+++b 

a++ +b

Cái nào là ok. Các ví dụ khác của bạn cũng vậy.


27
+1 Giải thích hay. Mặc dù vậy, tôi phải chọn: Nó đúng về mặt cú pháp, nó chỉ có một lỗi ngữ nghĩa (cố gắng tăng giá trị từ đó a++).

7
a++dẫn đến một giá trị.
Femaref

9
Trong ngữ cảnh của lexers, thuật toán 'tham lam' thường được gọi là Maximal Munch ( en.wikipedia.org/wiki/Maximal_munch ).
JoeG

14
Đẹp. Nhiều ngôn ngữ có những trường hợp góc kỳ lạ tương tự nhờ sự tham lam ghép nối. Đây là một điều thực sự kỳ lạ khi làm cho biểu thức dài hơn khiến nó trở nên tốt hơn: Trong VBScript x = 10&987&&654&&321là bất hợp pháp, nhưng kỳ lạ x = 10&987&&654&&&321là đủ hợp pháp.
Eric Lippert

1
Nó không liên quan gì đến lòng tham và tất cả đều liên quan đến trật tự và ưu tiên. ++ cao hơn thì + nên hai ++ sẽ được thực hiện trước. +++++ b cũng sẽ là + ++ ++ b chứ không phải ++ ++ + b. Ghi có vào @MByD cho liên kết.

30

Lexer sử dụng thuật toán thường được gọi là thuật toán "tối đa" để tạo mã thông báo. Điều đó có nghĩa là khi nó đang đọc các ký tự trong, nó sẽ tiếp tục đọc các ký tự cho đến khi nó gặp thứ gì đó không thể thuộc cùng một mã thông báo như những gì nó đã có (ví dụ: nếu nó đang đọc các chữ số thì những gì nó có là một số, nếu nó gặp phải an A, nó biết rằng đó không thể là một phần của số. vì vậy nó dừng lại và để lại Avùng đệm đầu vào để sử dụng làm phần đầu của mã thông báo tiếp theo). Sau đó, nó trả về mã thông báo đó cho trình phân tích cú pháp.

Trong trường hợp này, điều đó có nghĩa là +++++được viết bằng a ++ ++ + b. Vì bước tăng sau đầu tiên tạo ra một giá trị, nên không thể áp dụng giá trị thứ hai cho nó và trình biên dịch sẽ báo lỗi.

Chỉ cần FWIW, trong C ++, bạn có thể nạp chồng operator++để tạo ra một giá trị, cho phép điều này hoạt động. Ví dụ:

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;
}

Các biên dịch và chạy (mặc dù nó không làm gì cả) với các trình biên dịch C ++ mà tôi có (VC ++, g ++, Comeau).


1
"ví dụ: nếu nó đang đọc các chữ số thì những gì nó có là một số, nếu nó gặp chữ A, nó biết rằng không thể là một phần của số" 16FAlà một số thập lục phân hoàn toàn tốt có chứa A.
orlp

1
@nightcracker: có, nhưng không có 0xở đầu, nó sẽ vẫn coi đó là số 16theo sau FA, không phải là một số thập lục phân.
Jerry Coffin,

@Jerry Coffin: Bạn không nói rằng 0xkhông phải là một phần của con số.
orlp

@nightcracker: không, tôi không - vì hầu hết mọi người không coi là xmột chữ số, nó có vẻ khá không cần thiết.
Jerry Coffin,

14

Ví dụ chính xác này được đề cập trong dự thảo tiêu chuẩn C99 ( cùng chi tiết trong C11 ) phần 6.4 Các yếu tố đơn giản, đoạn 4 có nội dung:

Nếu luồng đầu vào đã được phân tích cú pháp thành mã thông báo tiền xử lý lên đến một ký tự nhất định, mã thông báo tiền xử lý tiếp theo là chuỗi ký tự dài nhất có thể tạo thành mã thông báo tiền xử lý. [...]

còn được gọi là quy tắc tối đa munch được sử dụng trong phân tích từ vựng để tránh sự mơ hồ và hoạt động bằng cách lấy càng nhiều phần tử càng tốt để tạo thành một mã thông báo hợp lệ.

đoạn văn cũng có hai ví dụ, ví dụ thứ hai là một đối sánh chính xác cho câu hỏi của bạn và như sau:

VÍ DỤ 2: Đoạn chương trình x +++++ y được phân tích cú pháp là x ++ ++ + y, điều này vi phạm một ràng buộc đối với toán tử tăng, mặc dù việc phân tích cú pháp x ++ + ++ y có thể mang lại một biểu thức đúng.

cho chúng ta biết rằng:

a+++++b

sẽ được phân tích cú pháp thành:

a ++ ++ + b

điều này vi phạm các ràng buộc về tăng số bài đăng vì kết quả của số tăng bài đăng đầu tiên là một giá trị và số tăng bài đăng yêu cầu một giá trị. Điều này được đề cập trong phần Các 6.5.2.4 toán tử tăng và giảm Postfix cho biết ( mỏ nhấn mạnh ):

Toán hạng của toán tử tăng hoặc giảm hậu tố phải có kiểu thực hoặc con trỏ đủ điều kiện hoặc không đủ tiêu chuẩn và phải là giá trị có thể sửa đổi.

Kết quả của toán tử postfix ++ là giá trị của toán hạng.

Cuốn sách Gotchas C ++ cũng đề cập đến trường hợp này trong Gotcha #17 Maximal Munch Problems, nó cũng là vấn đề tương tự trong C ++ và nó cũng đưa ra một số ví dụ. Nó giải thích rằng khi xử lý nhóm ký tự sau:

->*

máy phân tích từ vựng có thể thực hiện một trong ba việc:

  • Hãy đối xử với nó như ba thẻ: -, >*
  • Coi nó như hai mã thông báo: ->*
  • Coi nó như một mã thông báo: ->*

Các nhai tối đa quy định cho phép nó để tránh những sự mơ hồ. Tác giả chỉ ra rằng nó ( Trong ngữ cảnh C ++ ):

giải quyết nhiều vấn đề hơn nó gây ra, nhưng trong hai tình huống phổ biến, đó là một sự khó chịu.

Ví dụ đầu tiên sẽ là các mẫu có đối số mẫu cũng là mẫu ( đã được giải quyết trong C ++ 11 ), ví dụ:

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

Điều này diễn giải các dấu ngoặc nhọn đóng là toán tử shift , và do đó, cần có một khoảng trắng để phân biệt:

list< vector<string> > lovos;
                    ^

Trường hợp thứ hai liên quan đến các đối số mặc định cho con trỏ, ví dụ:

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

sẽ được hiểu là *=toán tử gán, giải pháp trong trường hợp này là đặt tên cho các tham số trong khai báo.


Bạn có biết phần nào của C ++ 11 nói quy tắc phân phối tối đa không? 2.2.3, 2.5.3 rất thú vị, nhưng không rõ ràng như C. >>Quy tắc được hỏi tại: stackoverflow.com/questions/15785496/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 xem câu trả lời này ở đây
Shafik Yaghmour

Rất cảm ơn, đó là một trong những phần tôi đã chỉ đến. Tôi sẽ ủng hộ bạn vào ngày mai khi mũ của tôi hết ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

12

Trình biên dịch của bạn cố gắng phân tích cú pháp a+++++bvà diễn giải nó thành (a++)++ +b. Bây giờ, kết quả của post-increment ( a++) không phải là lvalue , tức là nó không thể được post-tăng thêm nữa.

Xin đừng bao giờ viết mã như vậy trong các chương trình chất lượng sản xuất. Hãy nghĩ về những người nghèo đang đuổi theo bạn, những người cần diễn giải mã của bạn.


10
(a++)++ +b

a ++ trả về giá trị trước đó, một rvalue. Bạn không thể tăng số này.


7

Bởi vì nó gây ra hành vi không xác định.

Đó là cái nào?

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

Vâng, cả bạn và trình biên dịch đều không biết điều đó.

BIÊN TẬP:

Lý do thực sự là một trong những lý do như những người khác đã nói:

Nó được hiểu là (a++)++ + b.

nhưng tăng số bài đăng yêu cầu một giá trị (là một biến có tên) nhưng (a ++) trả về một giá trị không thể tăng lên, do đó dẫn đến thông báo lỗi bạn nhận được.

Thx cho những người khác để chỉ ra điều này.


5
bạn có thể nói tương tự đối với a +++ b - (a ++) + b và a + (++ b) có kết quả khác nhau.
Michael Chinen

4
trên thực tế, hậu tố ++ có mức độ ưu tiên cao hơn tiền tố ++, vì vậy a+++bluôn luôn như vậya++ + b
MByD

4
Tôi không nghĩ đây là câu trả lời đúng, nhưng tôi có thể sai. Tôi nghĩ rằng lexer định nghĩa nó là a++ ++ +bthứ không thể được phân tích cú pháp.
Lou Franco

2
Tôi không đồng ý với câu trả lời này. 'hành vi không xác định' khá khác với sự mơ hồ về mã hóa; và tôi cũng không nghĩ vấn đề là như vậy.
Jim Blackler

2
"Nếu không một +++++ b sẽ đánh giá để ((a ++) ++) + b" ... quan điểm của tôi ngay bây giờ là a+++++b không đánh giá để (a++)++)+b. Chắc chắn với GCC nếu bạn chèn các dấu ngoặc đó và xây dựng lại thì thông báo lỗi không thay đổi.
Jim Blackler

5

Tôi nghĩ rằng trình biên dịch coi nó là

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

++phải có dưới dạng toán hạng một giá trị có thể được sửa đổi. a là một giá trị có thể được sửa đổi. a++tuy nhiên là 'rvalue', nó không thể được sửa đổi.

Bằng cách này các lỗi tôi nhìn thấy trên GCC C là như nhau, nhưng khác nhau-worded: lvalue required as increment operand.


0

Thực hiện theo thứ tự sơ chế này

1. ++ (tăng trước)

2. + - (cộng hoặc trừ)

3. "x" + "y" thêm cả hai dãy

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.