I = (i, ++ i, 1) + 1; làm gì


174

Sau khi đọc câu trả lời này về hành vi và điểm trình tự không xác định, tôi đã viết một chương trình nhỏ:

#include <stdio.h>

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

Đầu ra là 2. Chúa ơi, tôi đã không thấy sự sụt giảm sắp tới! Chuyện gì đang xảy ra ở đây?

Ngoài ra, trong khi biên dịch mã trên, tôi nhận được một cảnh báo:

px.c: 5: 8: cảnh báo: toán hạng bên trái của biểu thức dấu phẩy không có hiệu lực

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

Tại sao? Nhưng có lẽ nó sẽ được trả lời tự động bằng câu trả lời cho câu hỏi đầu tiên của tôi.


289
Đừng làm những điều kỳ lạ, bạn sẽ không có bạn bè :(
Maroun

9
Thông điệp cảnh báo là câu trả lời cho câu hỏi đầu tiên của bạn.
Yu Hao

2
@gsamaras: không. giá trị kết quả bị loại bỏ, không phải là sửa đổi. câu trả lời thực sự: toán tử dấu phẩy tạo ra một điểm thứ tự.
Karoly Horvath

3
@gsamaras Bạn không nên quan tâm khi bạn có điểm tích cực và thậm chí nhiều hơn với hơn 10 câu hỏi.
LyingOnTheSky

9
Lưu ý: Trình biên dịch tối ưu hóa có thể đơn giản làmprintf("2\n");
chux - Phục hồi Monica

Câu trả lời:


256

Trong biểu thức (i, ++i, 1), dấu phẩy được sử dụng là toán tử dấu phẩy

toán tử dấu phẩy (được đại diện bởi mã thông báo ,) là toán tử nhị phân đánh giá toán hạng đầu tiên của nó và loại bỏ kết quả, sau đó đánh giá toán hạng thứ hai và trả về giá trị này (và loại).

Bởi vì nó loại bỏ toán hạng đầu tiên của nó, nó thường chỉ hữu ích khi toán hạng đầu tiên có tác dụng phụ mong muốn . Nếu hiệu ứng phụ cho toán hạng đầu tiên không diễn ra, thì trình biên dịch có thể tạo cảnh báo về biểu thức mà không có hiệu lực.

Vì vậy, trong biểu thức trên, phần ngoài cùng bên trái isẽ được ước tính và giá trị của nó sẽ bị loại bỏ. Sau đó ++isẽ được đánh giá và sẽ tăng thêm i1 và một lần nữa giá trị của biểu thức ++isẽ bị loại bỏ, nhưng tác dụng phụ ilà vĩnh viễn . Sau đó 1sẽ được đánh giá và giá trị của biểu thức sẽ là 1.

Nó tương đương với

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;  

Lưu ý rằng biểu thức trên là hoàn toàn hợp lệ và không gọi hành vi không xác định vì có một điểm thứ tự giữa việc đánh giá các toán hạng trái và phải của toán tử dấu phẩy.


1
mặc dù biểu thức cuối cùng là hợp lệ, không phải là biểu thức thứ hai ++ tôi là một hành vi không xác định? nó được đánh giá và giá trị của biến chưa được khởi tạo được tăng trước, điều này có đúng không? hoặc tôi đang thiếu một cái gì đó?
Koushik Shetty

2
@Koushik; iđược khởi tạo với 5. Nhìn vào tờ khai int i = 5;.
haccks

1
Ôi lỗi của tôi. Xin lỗi tôi thành thật không thấy điều đó.
Koushik Shetty

Có một sai lầm ở đây: ++ tôi sẽ tăng i sau đó đánh giá nó, trong khi i ++ sẽ đánh giá tôi sau đó tăng nó.
Quentin Hayot

1
@QuentinHayot; Gì? Bất kỳ tác dụng phụ diễn ra sau khi đánh giá biểu hiện. Trong trường hợp ++i, biểu thức này sẽ được ước tính, isẽ được tăng lên và giá trị tăng này sẽ là giá trị của biểu thức. Trong trường hợp i++, biểu thức này sẽ được ước tính, giá trị cũ isẽ là giá trị của biểu thức, isẽ được tăng lên bất cứ lúc nào giữa điểm trước và điểm tiếp theo của biểu thức.
haccks

62

Trích dẫn từ C11, chương 6.5.17, toán tử dấu phẩy

Toán hạng bên trái của toán tử dấu phẩy được đánh giá là biểu thức void; có một điểm thứ tự giữa đánh giá của nó và của toán hạng bên phải. Sau đó toán hạng bên phải được đánh giá; kết quả có loại và giá trị của nó.

Vì vậy, trong trường hợp của bạn,

(i, ++i, 1)

được đánh giá là

  1. i, được đánh giá là biểu thức void, giá trị bị loại bỏ
  2. ++i, được đánh giá là biểu thức void, giá trị bị loại bỏ
  3. cuối cùng, 1giá trị trả lại.

Vì vậy, tuyên bố cuối cùng trông giống như

i = 1 + 1;

iđược 2. Tôi đoán điều này trả lời cả hai câu hỏi của bạn,

  • Làm thế nào để icó được giá trị 2?
  • Tại sao có một thông điệp cảnh báo?

Lưu ý: FWIW, như có một điểm chuỗi hiện sau khi thẩm định của các toán hạng tay trái, một biểu thức như (i, ++i, 1)sẽ không invoke UB, là một thể thường nghĩ do nhầm lẫn.


+1 Sourav, vì điều này giải thích tại sao việc thâm nhập irõ ràng không có tác dụng! Tuy nhiên, tôi không nghĩ điều đó quá rõ ràng đối với một anh chàng không biết toán tử dấu phẩy (và tôi không biết cách tìm kiếm trợ giúp, ngoài việc hỏi một câu hỏi). Đáng tiếc tôi đã nhận được rất nhiều downvote! Tôi sẽ kiểm tra các câu trả lời khác và sau đó quyết định chấp nhận. Cảm ơn! Đẹp đầu câu trả lời btw.
gsamaras

Tôi cảm thấy tôi phải giải thích lý do tại sao tôi chấp nhận câu trả lời haccks. Tôi đã sẵn sàng chấp nhận bạn, vì nó thực sự trả lời cả hai câu hỏi của tôi. Tuy nhiên, nếu bạn kiểm tra các nhận xét về câu hỏi của tôi, bạn sẽ thấy rằng một số người không thể nhìn thấy ngay từ đầu tại sao điều này không gọi UB. câu trả lời haccks cung cấp một số thông tin có liên quan. Tất nhiên, tôi có câu trả lời liên quan đến UB trong câu hỏi của tôi, nhưng một số người có thể bỏ lỡ điều đó. Hy vọng bạn đồng ý với mong muốn của tôi, nếu không cho tôi biết. :)
gsamaras

30
i = (i, ++i, 1) + 1;

Hãy phân tích từng bước một.

(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

Vì vậy, chúng tôi có được 2. Và nhiệm vụ cuối cùng bây giờ:

i = 2;

Bất cứ điều gì trong tôi trước khi nó bị ghi đè.


Sẽ thật tốt khi nói rằng điều này xảy ra vì toán tử dấu phẩy. +1 cho phân tích từng bước mặc dù! Đẹp đầu câu trả lời btw.
gsamaras

Tôi xin lỗi vì giải thích không đầy đủ, tôi chỉ có một ghi chú ở đó ( ... nhưng bỏ qua, có ... ). Tôi muốn giải thích chủ yếu tại sao ++ikhông đóng góp vào kết quả.
dlask

bây giờ các vòng lặp của tôi sẽ luôn như thếint i = 0; for( ;(++i, i<max); )
CoffeDeveloper

19

Kết quả của

(i, ++i, 1)

1

Dành cho

(i,++i,1) 

việc đánh giá xảy ra sao cho người ,vận hành loại bỏ giá trị được đánh giá và sẽ giữ lại đúng giá trị nhất1

Vì thế

i = 1 + 1 = 2

1
Vâng tôi cũng nghĩ vậy, nhưng tôi không biết tại sao!
gsamaras

@gsamaras vì toán tử dấu phẩy đánh giá thuật ngữ trước nhưng loại bỏ nó (nghĩa là không sử dụng nó cho các bài tập hoặc tương tự)
Marco A.

14

Bạn sẽ tìm thấy một số đọc tốt trên trang wiki cho toán tử Dấu phẩy .

Về cơ bản, nó

... Đánh giá toán hạng đầu tiên của nó và loại bỏ kết quả, sau đó đánh giá toán hạng thứ hai và trả về giá trị này (và loại).

Điều này có nghĩa rằng

(i, i++, 1)

lần lượt sẽ đánh giá i, loại bỏ kết quả, đánh giá i++, loại bỏ kết quả và sau đó đánh giá và trả lại 1.


O_O địa ngục, không cú pháp đó là hợp lệ trong C ++, tôi nhớ tôi đã vài nơi tôi cần cú pháp (về cơ bản tôi đã viết: (void)exp; a= exp2;trong khi tôi chỉ cần a = exp, exp2;)
CoffeDeveloper

13

Bạn cần biết toán tử dấu phẩy đang làm gì ở đây:

Biểu hiện của bạn:

(i, ++i, 1)

Biểu thức đầu tiên i, được ước tính, biểu thức thứ hai ++i, được ước tính và biểu thức thứ ba 1, được trả về cho toàn bộ biểu thức.

Vì vậy, kết quả là : i = 1 + 1.

Đối với câu hỏi phần thưởng của bạn, như bạn thấy, biểu thức đầu tiên ikhông có tác dụng gì cả, vì vậy trình biên dịch phàn nàn.


5

Dấu phẩy có một ưu tiên 'nghịch đảo'. Đây là những gì bạn sẽ nhận được từ những cuốn sách cũ và sách hướng dẫn C từ IBM (thập niên 70/80). Vì vậy, 'lệnh' cuối cùng là những gì được sử dụng trong biểu thức cha.

Trong C hiện đại, việc sử dụng của nó rất lạ nhưng rất thú vị ở C cũ (ANSI):

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

Trong khi tất cả các hoạt động (hàm) được gọi từ trái sang phải, chỉ biểu thức cuối cùng sẽ được sử dụng như là kết quả của 'while' có điều kiện. Điều này ngăn việc xử lý 'goto' để giữ một khối lệnh duy nhất chạy trước khi kiểm tra điều kiện.

EDIT: Điều này cũng tránh một cuộc gọi đến một hàm xử lý có thể xử lý tất cả logic ở các toán hạng bên trái và do đó trả về kết quả logic. Hãy nhớ rằng, chúng tôi đã không có chức năng nội tuyến trong quá khứ của C. Vì vậy, điều này có thể tránh được một cuộc gọi trên cao.


Luciano, bạn cũng có liên kết đến câu trả lời này: stackoverflow.com/questions/17902992/ .
gsamaras

Vào đầu những năm 90 trước các chức năng nội tuyến, tôi đã sử dụng nó rất nhiều để tối ưu hóa và giữ cho mã được tổ chức.
Luciano
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.