i = อะไร (i, ++ i, 1) + 1; ทำ?


174

หลังจากอ่านคำตอบนี้เกี่ยวกับพฤติกรรมที่ไม่ได้กำหนดและจุดลำดับฉันได้เขียนโปรแกรมขนาดเล็ก:

#include <stdio.h>

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

2เอาท์พุทเป็น โอ้พระเจ้าฉันไม่เห็นการลดลงมา! เกิดอะไรขึ้นที่นี่

นอกจากนี้ในขณะที่รวบรวมโค้ดข้างต้นฉันได้รับคำเตือนว่า:

px.c: 5: 8: คำเตือน: ตัวถูกดำเนินการทางซ้ายของนิพจน์จุลภาคไม่มีผลใด ๆ

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

ทำไม? แต่อาจจะตอบโดยอัตโนมัติโดยคำตอบของคำถามแรกของฉัน


289
อย่าทำสิ่งแปลกที่คุณจะไม่มีเพื่อน :(
Maroun

9
ข้อความเตือนคือคำตอบสำหรับคำถามแรกของคุณ
Yu Hao

2
@gsamaras: ไม่ ค่าผลลัพธ์จะถูกละทิ้งไม่ใช่การดัดแปลง คำตอบที่แท้จริง: ผู้ประกอบการคอมม่าสร้างจุดลำดับ
Karoly Horvath

3
@gsamaras คุณไม่ควรสนใจเมื่อคุณมีคะแนนบวกและมีมากกว่า 10 คำถาม
LyingOnTheSky

9
หมายเหตุ: การเพิ่มประสิทธิภาพของคอมไพเลอร์ง่ายอาจทำprintf("2\n");
Chux - คืนสิทธิ์ให้กับโมนิกา

คำตอบ:


256

ในนิพจน์(i, ++i, 1)เครื่องหมายจุลภาคที่ใช้คือตัวดำเนินการเครื่องหมายจุลภาค

ตัวดำเนินการเครื่องหมายจุลภาค (แสดงโดยโทเค็น,) เป็นตัวดำเนินการไบนารีที่ประเมินตัวถูกดำเนินการแรกและทิ้งผลลัพธ์จากนั้นประเมินตัวถูกดำเนินการที่สองและส่งกลับค่านี้ (และประเภท)

เนื่องจากมันทิ้งตัวถูกดำเนินการตัวแรกโดยทั่วไปจะมีประโยชน์เฉพาะเมื่อตัวถูกดำเนินการตัวแรกมีผลข้างเคียงที่ต้องการ หากไม่มีผลข้างเคียงกับตัวถูกดำเนินการตัวแรกคอมไพเลอร์อาจสร้างคำเตือนเกี่ยวกับนิพจน์โดยไม่มีผลกระทบ

ดังนั้นในนิพจน์ด้านบนซ้ายสุดiจะถูกประเมินและมูลค่าของมันจะถูกทิ้ง จากนั้น++iจะมีการประเมินและจะเพิ่มiขึ้น 1 ครั้งและอีกครั้งค่าของนิพจน์ที่++iจะถูกทิ้งแต่ผลข้างเคียงที่จะiเป็นแบบถาวร จากนั้นจะมีการประเมินและความคุ้มค่าของการแสดงออกจะเป็น 11

มันเทียบเท่ากับ

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;  

โปรดทราบว่าการแสดงออกดังกล่าวข้างต้นถูกต้องสมบูรณ์และไม่ก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนดเพราะมีจุดลำดับระหว่างการประเมินผลการดำเนินการด้านซ้ายและขวาของผู้ประกอบการเครื่องหมายจุลภาค


1
แม้ว่านิพจน์สุดท้ายจะใช้ได้ แต่นิพจน์ที่สอง ++ ฉันเป็นพฤติกรรมที่ไม่ได้กำหนดหรือไม่ มันถูกประเมินและค่าของตัวแปร uninitialized จะถูกเพิ่มค่าล่วงหน้าซึ่งไม่ถูกต้อง? หรือฉันหายไปบางอย่าง
Koushik Shetty

2
@Koushik; จะเริ่มต้นด้วยi ดูคำสั่งประกาศ5 int i = 5;
haccks

1
โอ้ฉันไม่ดี ขอโทษที่ฉันจริง ๆ แล้วเห็นว่า
Koushik Shetty

มีข้อผิดพลาดที่นี่: ++ ฉันจะเพิ่มขึ้นจากนั้นฉันประเมินมันในขณะที่ i ++ จะประเมินฉันแล้วจึงเพิ่มขึ้น
Quentin Hayot

1
@QuentinHayot; อะไร? ผลข้างเคียงใด ๆ เกิดขึ้นหลังจากการประเมินผลของการแสดงออก ในกรณีของ++iนิพจน์นี้จะถูกประเมินผลiจะเพิ่มขึ้นและมูลค่าที่เพิ่มขึ้นนี้จะเป็นค่าของการแสดงออก ในกรณีของi++นิพจน์นี้จะถูกประเมินค่าเก่าของiจะเป็นค่าของนิพจน์iจะเพิ่มขึ้นเมื่อใดก็ได้ระหว่างจุดลำดับก่อนหน้าและถัดไปของนิพจน์
haccks

62

การอ้างอิงจากC11, บท6.5.17, เครื่องหมายจุลภาค

ตัวถูกดำเนินการทางซ้ายของโอเปอเรเตอร์คอมม่าถูกประเมินเป็นนิพจน์โมฆะ มีจุดลำดับระหว่างการประเมินกับตัวถูกดำเนินการด้านขวา จากนั้นจะทำการประเมินตัวถูกดำเนินการที่เหมาะสม ผลลัพธ์มีประเภทและค่าของมัน

ดังนั้นในกรณีของคุณ

(i, ++i, 1)

ได้รับการประเมินว่าเป็น

  1. iได้รับการประเมินว่าเป็นโมฆะนิพจน์ค่าที่ถูกทิ้ง
  2. ++iได้รับการประเมินว่าเป็นโมฆะนิพจน์ค่าที่ถูกทิ้ง
  3. ในที่สุด1มูลค่าที่ส่งคืน

ดังนั้นคำสั่งสุดท้ายดูเหมือนว่า

i = 1 + 1;

และได้รับการi 2ฉันเดาคำตอบนี้ทั้งสองคำถามของคุณ

  • วิธีการiรับค่า 2?
  • เหตุใดจึงมีข้อความเตือน

หมายเหตุ: FWIW เนื่องจากมีจุดลำดับอยู่หลังจากการประเมินตัวถูกดำเนินการทางซ้ายการแสดงออกเช่น(i, ++i, 1)นี้จะไม่เรียก UB เนื่องจากคนทั่วไปอาจคิดผิด


+1 Sourav เนื่องจากเป็นการอธิบายว่าทำไมการอินทราเน็ตของiชัดเจนจึงไม่มีผล! อย่างไรก็ตามฉันไม่คิดว่ามันชัดเจนนักสำหรับผู้ชายที่ไม่รู้จักเครื่องหมายจุลภาค (และฉันไม่รู้วิธีค้นหาความช่วยเหลือนอกเหนือจากการถามคำถาม) น่าเสียดายที่ฉันมีคะแนนโหวตต่ำมาก! ฉันจะตรวจสอบคำตอบอื่น ๆ แล้วตัดสินใจเลือกที่จะยอมรับ ขอบคุณ! คำตอบที่ดี btw
gsamaras

ฉันรู้สึกว่าฉันต้องอธิบายว่าทำไมฉันจึงยอมรับคำตอบของ haccks ฉันพร้อมที่จะยอมรับของคุณเพราะมันตอบคำถามทั้งสองของฉันอย่างแท้จริง อย่างไรก็ตามหากคุณตรวจสอบความคิดเห็นของคำถามของฉันคุณจะเห็นว่าบางคนไม่สามารถมองเห็นได้อย่างรวดเร็วในครั้งแรกว่าทำไมไม่เรียกใช้ UB คำตอบของ haccks ให้ข้อมูลที่เกี่ยวข้อง แน่นอนฉันมีคำตอบเกี่ยวกับการเชื่อมโยง UB ในคำถามของฉัน แต่บางคนอาจพลาด หวังว่าคุณจะเห็นด้วยกับความต้องการของฉันถ้าไม่แจ้งให้เราทราบ :)
gsamaras

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

มาวิเคราะห์กันทีละขั้นตอน

(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

ดังนั้นเราจึงได้รับ 2. และการมอบหมายขั้นสุดท้ายตอนนี้:

i = 2;

สิ่งที่อยู่ในฉันก่อนที่มันจะถูกเขียนทับในตอนนี้


มันจะดีที่จะระบุว่าสิ่งนี้เกิดขึ้นเพราะผู้ประกอบการเครื่องหมายจุลภาค +1 สำหรับการวิเคราะห์ทีละขั้นตอน! คำตอบที่ดี btw
gsamaras

ฉันขอโทษสำหรับคำอธิบายที่ไม่เพียงพอฉันมีเพียงบันทึกที่นั่น ( ... แต่ไม่สนใจมี ... ) ฉันต้องการอธิบายว่าทำไมส่วนใหญ่ถึง++iไม่ทำให้เกิดผลลัพธ์
dlask

ตอนนี้ลูปของฉันจะเป็นเช่นนี้เสมอint i = 0; for( ;(++i, i<max); )
CoffeDeveloper

19

ผลของการ

(i, ++i, 1)

คือ

1

สำหรับ

(i,++i,1) 

การประเมินเกิดขึ้นเช่นว่า,ผู้ประกอบการทิ้งมูลค่าการประเมินและจะรักษาเพียงมูลค่าที่เหมาะสมที่สุดซึ่งก็คือ1

ดังนั้น

i = 1 + 1 = 2

1
ใช่ฉันคิดอย่างนั้นเหมือนกัน แต่ฉันไม่รู้ว่าทำไม!
gsamaras

เพราะผู้ประกอบการ @gsamaras จุลภาคประเมินระยะก่อนหน้านี้ แต่ทิ้งมัน (คือไม่ได้ใช้มันสำหรับการมอบหมายงานหรือชอบ)
มาร์โกเอ

14

คุณจะได้พบบางอย่างการอ่านที่ดีในหน้าวิกิพีเดียสำหรับผู้ประกอบการจุลภาค

โดยทั่วไปมัน

... ประเมินค่าตัวถูกดำเนินการแรกและทิ้งผลลัพธ์แล้วประเมินค่าตัวถูกดำเนินการตัวที่สองและส่งคืนค่านี้ (และประเภท)

ซึ่งหมายความว่า

(i, i++, 1)

ในที่สุดก็จะประเมินiทิ้งผลประเมินทิ้งผลและประเมินผลการศึกษาแล้วและผลตอบแทนi++1


O_O นรกว่าไวยากรณ์นั้นถูกต้องใน C ++ ฉันจำได้ว่าฉันมีไม่กี่แห่งที่ฉันต้องการไวยากรณ์นั้น (โดยทั่วไปฉันเขียนว่า: (void)exp; a= exp2;ในขณะที่ฉันต้องการa = exp, exp2;)
CoffeDeveloper

13

คุณต้องรู้ว่าตัวดำเนินการจุลภาคกำลังทำอะไรที่นี่:

การแสดงออกของคุณ:

(i, ++i, 1)

นิพจน์แรกi, ได้รับการประเมิน, นิพจน์ที่สอง++i, ได้รับการประเมินและนิพจน์ที่สาม1จะถูกส่งคืนสำหรับนิพจน์ทั้งหมด

i = 1 + 1ดังนั้นผลที่ได้คือ:

สำหรับคำถามโบนัสของคุณอย่างที่เห็นนิพจน์แรกiนั้นไม่มีผลเลยคอมไพเลอร์ก็บ่น


5

เครื่องหมายจุลภาคมีความสำคัญ 'แบบผกผัน' นี่คือสิ่งที่คุณจะได้รับจากหนังสือเก่าและคู่มือ C จาก IBM (70s / 80s) ดังนั้น 'คำสั่ง' สุดท้ายคือสิ่งที่ใช้ในการแสดงออกของผู้ปกครอง

ในปัจจุบัน C การใช้งานของมันแปลก แต่น่าสนใจมากใน C เก่า (ANSI):

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

ในขณะที่การดำเนินการทั้งหมด (ฟังก์ชั่น) ถูกเรียกใช้จากซ้ายไปขวาเฉพาะการแสดงออกครั้งสุดท้ายเท่านั้นที่จะถูกใช้เป็นผลมาจากเงื่อนไข 'ขณะที่' วิธีนี้ช่วยป้องกันการจัดการ 'goto's เพื่อให้บล็อกคำสั่งที่ไม่ซ้ำกันทำงานก่อนการตรวจสอบสภาพ

แก้ไข: นี่หลีกเลี่ยงการเรียกไปยังฟังก์ชั่นการจัดการซึ่งสามารถดูแลตรรกะทั้งหมดทางด้านซ้ายของตัวถูกดำเนินการและกลับผลลัพธ์ตรรกะ โปรดจำไว้ว่าเราไม่ได้ทำงานแบบอินไลน์ในอดีตที่ผ่านมาของ C ดังนั้นสิ่งนี้สามารถหลีกเลี่ยงค่าใช้จ่ายการโทร


ลูเซียโนคุณได้นอกจากนี้ยังเชื่อมโยงไปยังคำตอบนี้: stackoverflow.com/questions/17902992/...
gsamaras

ในช่วงต้นยุค 90 ก่อนหน้าที่อินไลน์ฉันใช้มันบ่อยครั้งเพื่อปรับแต่งและจัดระเบียบโค้ด
Luciano
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.