ลำดับการประเมินดัชนีอาเรย์ (กับนิพจน์) ใน C


47

ดูรหัสนี้:

static int global_var = 0;

int update_three(int val)
{
    global_var = val;
    return 3;
}

int main()
{
    int arr[5];
    arr[global_var] = update_three(2);
}

รายการอาร์เรย์ใดที่ได้รับการอัปเดต 0 หรือ 2

มีส่วนหนึ่งในสเปคของ C ที่บ่งบอกถึงความสำคัญของการดำเนินงานในกรณีนี้หรือไม่?


21
นี่เป็นกลิ่นของพฤติกรรมที่ไม่ได้กำหนด แน่นอนว่าเป็นสิ่งที่ไม่ควรเขียนรหัสอย่างจงใจ
Bidd Fiddling

1
ฉันยอมรับว่ามันเป็นตัวอย่างของการเข้ารหัสที่ไม่ดี
Jiminion

4
ผลลัพธ์เล็ก ๆ น้อย ๆ คือgodbolt.org/z/hM2Jo2
Bob__

15
สิ่งนี้ไม่เกี่ยวกับดัชนีอาเรย์หรือลำดับการดำเนินการ มันเกี่ยวข้องกับสิ่งที่ C spec เรียกว่า "sequence points" และโดยเฉพาะอย่างยิ่งความจริงที่ว่านิพจน์การมอบหมายไม่ได้สร้างจุดลำดับระหว่างนิพจน์ทางซ้ายและขวามือดังนั้นคอมไพเลอร์จึงมีอิสระที่จะทำ Chooses
Lee Daniel Crocker

4
คุณควรรายงานคำขอคุณลักษณะเพื่อclangให้ส่วนนี้ของรหัสก่อให้เกิดการเตือน IMHO
malat

คำตอบ:


51

คำสั่งของผู้ดำเนินการด้านซ้ายและขวา

ในการดำเนินการกำหนดในarr[global_var] = update_three(2)การใช้งาน C จะต้องประเมินตัวถูกดำเนินการและเป็นผลข้างเคียงให้อัพเดตค่าที่เก็บไว้ของตัวถูกดำเนินการด้านซ้าย C 2018 6.5.16 (ซึ่งเป็นเรื่องเกี่ยวกับการมอบหมาย) วรรค 3 บอกเราว่าไม่มีลำดับในตัวถูกดำเนินการทางซ้ายและขวา:

การประเมินผลของตัวถูกดำเนินการจะไม่เกิดขึ้นตามมา

นี่หมายถึงการนำ C ไปใช้เพื่อคำนวณlvalue arr[global_var]ก่อน (โดยการคำนวณ lvalue เราหมายถึงการหาสิ่งที่นิพจน์นี้อ้างถึง) จากนั้นประเมินupdate_three(2)และสุดท้ายกำหนดค่าของหลังให้กับอดีต หรือเพื่อประเมินupdate_three(2)ก่อนแล้วคำนวณค่า lvalue จากนั้นกำหนดค่าเดิมให้กับหลัง หรือเพื่อประเมินค่า lvalue และupdate_three(2)ในรูปแบบผสมบางอย่างแล้วกำหนดค่าที่ถูกต้องให้กับ lvalue ทางซ้าย

ในทุกกรณีการกำหนดค่าให้กับค่า lvalue จะต้องมาเป็นลำดับสุดท้ายเพราะ 6.5.16 3 ก็บอกว่า:

…ผลข้างเคียงของการอัพเดตค่าที่เก็บไว้ของตัวถูกดำเนินการด้านซ้ายจะถูกจัดลำดับหลังจากการคำนวณค่าของตัวถูกดำเนินการด้านซ้ายและขวา ...

การละเมิดลำดับ

บางคนอาจไตร่ตรองเกี่ยวกับพฤติกรรมที่ไม่ได้กำหนดเนื่องจากทั้งการใช้global_varและการอัปเดตแยกต่างหากโดยละเมิด 6.5 2 ซึ่งกล่าวว่า:

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

ผู้ปฏิบัติงาน C หลายคนคุ้นเคยกันดีว่าพฤติกรรมของนิพจน์เช่นx + x++ไม่ได้ถูกกำหนดโดยมาตรฐาน C เนื่องจากทั้งคู่ใช้ค่าของxและปรับเปลี่ยนมันในนิพจน์เดียวกันโดยไม่ต้องเรียงลำดับ อย่างไรก็ตามในกรณีนี้เรามีการเรียกใช้ฟังก์ชันซึ่งมีการจัดลำดับบางอย่าง global_varจะใช้ในการและมีการปรับปรุงในการเรียกฟังก์ชั่นarr[global_var]update_three(2)

6.5.2.2 10 บอกเราว่ามีจุดลำดับก่อนที่จะเรียกใช้ฟังก์ชัน:

มีจุดลำดับหลังจากการประเมินผลของฟังก์ชั่น designator และข้อโต้แย้งที่เกิดขึ้นจริง แต่ก่อนที่จะโทรจริง ...

ภายในฟังก์ชั่นglobal_var = val;คือการแสดงออกอย่างเต็มรูปแบบและเพื่อเป็น3ในreturn 3;ต่อ 6.8 4:

การแสดงออกแบบเต็มคือการแสดงออกที่ไม่ได้เป็นส่วนหนึ่งของการแสดงออกอื่นหรือส่วนหนึ่งของผู้ประกาศหรือผู้ประกาศนามธรรม ...

จากนั้นจะมีจุดลำดับระหว่างนิพจน์ทั้งสองนี้อีกครั้งต่อ 6.8 4:

…มีจุดลำดับระหว่างการประเมินนิพจน์เต็มกับการประเมินนิพจน์เต็มถัดไปที่จะประเมิน

ดังนั้นการดำเนินการ C อาจประเมินarr[global_var]ก่อนแล้วจึงเรียกใช้ฟังก์ชันซึ่งในกรณีที่มีจุดลำดับระหว่างพวกเขาเพราะมีหนึ่งก่อนการเรียกใช้ฟังก์ชั่นหรืออาจประเมินglobal_var = val;ในการเรียกใช้ฟังก์ชั่นแล้วarr[global_var]ในกรณีที่มี จุดลำดับระหว่างพวกเขาเพราะมีหนึ่งหลังจากการแสดงออกเต็มรูปแบบ ดังนั้นจึงไม่มีการระบุพฤติกรรม - อย่างใดอย่างหนึ่งในสองสิ่งนี้อาจได้รับการประเมินก่อน - แต่ไม่ได้กำหนดไว้


24

ผลที่ตามมาที่นี่เป็นที่ที่ไม่ได้ระบุ

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

นอกจากนี้ไม่ได้global_varไม่ได้กำหนดพฤติกรรมที่นี่เพราะสายงานแนะนำจุดตามลำดับเช่นเดียวกับทุกคำสั่งในการทำงานรวมถึงหนึ่งที่ปรับเปลี่ยน

เพื่อชี้แจงมาตรฐาน Cกำหนดพฤติกรรมที่ไม่ได้กำหนดในส่วน 3.4.3 เป็น:

พฤติกรรมที่ไม่ได้กำหนด

พฤติกรรมเมื่อมีการใช้โปรแกรมที่ไม่สามารถสร้างได้หรือผิดพลาดหรือข้อมูลผิดพลาดซึ่งมาตรฐานสากลนี้ไม่มีข้อกำหนด

และกำหนดพฤติกรรมที่ไม่ระบุในส่วน 3.4.4 เป็น:

พฤติกรรมที่ไม่ระบุ

การใช้ค่าที่ไม่ระบุรายละเอียดหรือพฤติกรรมอื่น ๆ ที่มาตรฐานสากลนี้ให้ความเป็นไปได้สองอย่างหรือมากกว่านั้นและไม่มีข้อกำหนดเพิ่มเติมใด ๆ ซึ่งถูกเลือกในกรณีใด ๆ

มาตรฐานระบุว่าลำดับการประเมินผลของฟังก์ชันอาร์กิวเมนต์ไม่ได้ระบุซึ่งในกรณีนี้หมายความว่าจะarr[0]ได้รับการตั้งค่าเป็น 3 หรือarr[2]ได้รับการตั้งค่าเป็น 3


“ การเรียกใช้ฟังก์ชันแนะนำจุดลำดับ” ไม่เพียงพอ ถ้าตัวถูกดำเนินการทางด้านซ้ายถูกประเมินก่อนก็จะพอเพียงตั้งแต่นั้นจุดลำดับจะแยกตัวถูกดำเนินการด้านซ้ายออกจากการประเมินในฟังก์ชัน แต่ถ้าตัวถูกดำเนินการทางด้านซ้ายถูกประเมินหลังจากการเรียกฟังก์ชันจุดลำดับเนื่องมาจากการเรียกฟังก์ชันจะไม่อยู่ระหว่างการประเมินในฟังก์ชันและการประเมินผลตัวถูกดำเนินการด้านซ้าย คุณต้องมีจุดลำดับที่คั่นนิพจน์ทั้งหมดด้วย
Eric Postpischil

2
@EricPostpischil ในคำศัพท์พรี C11 มีจุดลำดับในการเข้าและออกจากฟังก์ชั่น ในคำศัพท์ C11 ฟังก์ชันของฟังก์ชันทั้งหมดจะถูกจัดลำดับโดยไม่เกี่ยวข้องกับบริบทการเรียก สิ่งเหล่านี้ทั้งสองระบุสิ่งเดียวกันโดยใช้คำที่ต่างกัน
MM

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

1
@ kuroineko เพียงแค่ผลลัพธ์ที่ได้อาจแตกต่างกันไปโดยอัตโนมัติไม่ทำให้พฤติกรรมไม่ได้กำหนด มาตรฐานมีคำจำกัดความที่แตกต่างกันสำหรับพฤติกรรมที่ไม่ได้กำหนดและที่ไม่ได้ระบุและในสถานการณ์นี้มันเป็นสิ่งหลัง
dbush

@EricPostpischil คุณมีจุดลำดับที่นี่ (จากภาคผนวก C11 ข้อมูลภาคผนวก C): "ระหว่างการประเมินผลของตัวออกแบบฟังก์ชั่นและข้อโต้แย้งที่เกิดขึ้นจริงในการเรียกใช้ฟังก์ชั่นและการโทรจริง (6.5.2.2)", "ระหว่างการประเมินผล และนิพจน์แบบเต็มถัดไปที่จะถูกประเมิน ... / - / ... นิพจน์ (ทางเลือก) ในการส่งคืนคำสั่ง (6.8.6.4) " และในแต่ละอัฒภาคด้วยเช่นกันเพราะนั่นเป็นการแสดงออกที่สมบูรณ์
Lundin

1

ฉันพยายามและฉันได้รับการอัพเดตรายการ 0

อย่างไรก็ตามตามคำถามนี้: ด้านขวามือของนิพจน์จะถูกประเมินก่อนเสมอ

ลำดับของการประเมินผลไม่ระบุและไม่เป็นลำดับ ดังนั้นฉันคิดว่าควรหลีกเลี่ยงรหัสเช่นนี้


ฉันได้รับการอัพเดตที่รายการ 0 เช่นกัน
Jiminion

1
พฤติกรรมไม่ได้ถูกกำหนด แต่ไม่ได้ระบุ ตามธรรมชาติขึ้นอยู่กับทั้งควรหลีกเลี่ยง
Antti Haapala

@AnttiHaapala ฉันแก้ไขแล้ว
Mickael B.

1
อืมมม. และมันไม่ได้เกิดขึ้นตามลำดับ แต่จะถูกจัดลำดับตามลำดับ ... 2 คนที่ยืนอยู่ในคิวแบบสุ่มจะถูกจัดลำดับแบบไม่แน่นอน Neo Inside Agent Smith ไม่ได้เกิดขึ้นตามมาและพฤติกรรมที่ไม่ได้กำหนดจะเกิดขึ้น
Antti Haapala

0

เนื่องจากมีความรู้สึกเล็กน้อยที่จะปล่อยโค้ดสำหรับการกำหนดค่าก่อนที่คุณจะมีค่าที่จะกำหนดคอมไพเลอร์ C ส่วนใหญ่จะปล่อยโค้ดที่เรียกฟังก์ชันก่อนและบันทึกผลลัพธ์ที่ใดที่หนึ่ง (register, stack ฯลฯ ) จากนั้นพวกเขาจะปล่อยโค้ดที่ เขียนค่านี้ไปยังปลายทางสุดท้ายและดังนั้นพวกเขาจะอ่านตัวแปรทั่วโลกหลังจากที่มีการเปลี่ยนแปลง ให้เราเรียกสิ่งนี้ว่า "ลำดับธรรมชาติ" ซึ่งไม่ได้กำหนดโดยมาตรฐานใด ๆ แต่ด้วยตรรกะที่บริสุทธิ์

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

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

int idx = global_var;
arr[idx] = update_three(2);

หรือการเข้ารหัส:

int temp = update_three(2);
arr[global_var] = temp;

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

int result = global_var + (2 * global_var);
// Is not guaranteed to be equal to `3 * global_var`!

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

(ในกรณีที่มีคนถามว่าทำไมทุกคนจะทำx + 2 * xแทน3 * x- ในการเพิ่มซีพียูบางอย่างนั้นเร็วมากและการคูณด้วยกำลังสองเนื่องจากคอมไพเลอร์จะเปลี่ยนสิ่งเหล่านี้เป็นบิตกะ ( 2 * x == x << 1) แต่การคูณด้วยตัวเลขโดยพลการอาจช้ามาก ดังนั้นแทนที่จะคูณด้วย 3 คุณจะได้รับรหัสเร็วขึ้นโดยการเลื่อนบิต x 1 และเพิ่ม x ในผลลัพธ์ - และแม้กระทั่งเคล็ดลับนั้นจะดำเนินการโดยคอมไพเลอร์สมัยใหม่หากคุณคูณ 3 ด้วยและเปิดใช้การเพิ่มประสิทธิภาพเชิงรุก ซีพียูที่การคูณนั้นเร็วพอ ๆ กับการเพิ่มหลังจากนั้นกลจะทำให้การคำนวณช้าลง)


2
มันไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนด - มาตรฐานแสดงรายการความเป็นไปได้และหนึ่งในนั้นถูกเลือกในทุกกรณี
Antti Haapala

คอมไพเลอร์จะไม่เปลี่ยน3 * xเป็น x อ่านสองอัน มันอาจอ่าน x หนึ่งครั้งแล้วทำวิธี x + 2 * x บน register มันอ่าน x เป็น
MM

6
@Mecki "ถ้าคุณไม่สามารถบอกได้ว่าผลลัพธ์คืออะไรโดยการดูที่โค้ดผลลัพธ์นั้นจะไม่ได้กำหนด" - พฤติกรรมที่ไม่ได้กำหนดมีความหมายที่เฉพาะเจาะจงมากใน C / C ++ และนั่นไม่ใช่สิ่งนั้น answerers อื่น ๆ ที่ได้อธิบายว่าทำไมกรณีนี้โดยเฉพาะอย่างยิ่งที่ไม่ได้ระบุแต่ไม่ได้กำหนด
marcelm

3
ฉันขอขอบคุณที่มีความตั้งใจที่จะใช้แสงสว่างภายในตัวคอมพิวเตอร์แม้ว่ามันจะเกินขอบเขตของคำถามดั้งเดิม อย่างไรก็ตาม UB นั้นเป็นศัพท์แสง C / C ++ ที่แม่นยำมากและควรใช้อย่างระมัดระวังโดยเฉพาะอย่างยิ่งเมื่อคำถามเกี่ยวกับเทคนิคภาษา คุณอาจพิจารณาใช้คำว่า "พฤติกรรมที่ไม่ระบุ" ที่เหมาะสมแทนซึ่งจะช่วยปรับปรุงคำตอบอย่างมีนัยสำคัญ
kuroi neko

2
@Mecki " ไม่ได้กำหนดมีความหมายพิเศษมากในภาษาอังกฤษ " ... แต่ในคำถามที่มีป้ายกำกับlanguage-lawyerซึ่งภาษาที่เป็นปัญหานั้นมี"ความหมายพิเศษมาก" ของตัวเองสำหรับการไม่ได้กำหนดคุณจะทำให้สับสนโดยไม่ได้ใช้ คำจำกัดความของภาษา
TripeHound

-1

การแก้ไขทั่วโลก: พวกขอโทษฉันหมดกำลังใจและเขียนเรื่องไร้สาระมากมาย แค่พูดจาโผงผางแก่คนแก่

ฉันอยากจะเชื่อว่า C นั้นไม่ได้รับการยกเว้น แต่อนิจจาตั้งแต่ C11 มันถูกนำมาเทียบกับ C ++ เห็นได้ชัดว่าการรู้ว่าคอมไพเลอร์จะทำอย่างไรกับผลข้างเคียงในการแสดงออกตอนนี้ต้องแก้ปริศนาคณิตศาสตร์เล็ก ๆ น้อย ๆ ที่เกี่ยวข้องกับการเรียงลำดับของรหัสบางส่วนบนพื้นฐานของ "ตั้งอยู่ก่อนจุดประสาน"

ฉันบังเอิญได้ออกแบบและติดตั้งระบบฝังตัวตามเวลาจริงที่สำคัญสองสามวันในช่วง K&R (รวมถึงตัวควบคุมของรถยนต์ไฟฟ้าที่สามารถส่งคนชนเข้ามาในกำแพงที่ใกล้ที่สุดหากเครื่องยนต์ไม่ได้ถูกเก็บไว้ในอุตสาหกรรม 10 ตัน หุ่นยนต์ที่สามารถสควอชคนไปที่เยื่อกระดาษหากไม่ได้รับคำสั่งอย่างถูกต้องและชั้นของระบบที่ไม่เป็นอันตรายจะมีโปรเซสเซอร์จำนวนไม่กี่โหลที่ดูดบัสข้อมูลของพวกเขาให้แห้งด้วยค่าใช้จ่ายระบบน้อยกว่า 1%)

ฉันอาจแก่เกินไปหรือโง่เกินกว่าที่จะรับความแตกต่างระหว่างที่ไม่ได้กำหนดและไม่ได้ระบุ แต่ฉันคิดว่าฉันยังคงมีความคิดที่ดีเกี่ยวกับการดำเนินการพร้อมกันและการเข้าถึงข้อมูล จากความเห็นที่ได้รับการบอกกล่าวของฉันการครอบงำจิตใจของ C ++ และตอนนี้พวก C กับภาษาสัตว์เลี้ยงของพวกเขาในการแก้ไขปัญหาการซิงโครไนซ์เป็นความฝันที่มีค่าใช้จ่ายสูง ไม่ว่าคุณจะรู้ว่าการดำเนินการพร้อมกันคืออะไรและคุณไม่ต้องการกิซโมเหล่านี้หรือคุณไม่ต้องการและคุณจะทำสิ่งต่าง ๆ บนโลกใบนี้โดยไม่พยายามยุ่งกับมัน

รถบรรทุกที่บรรทุกสิ่งกีดขวางหน่วยความจำที่สะดุดตาทั้งหมดนี้เกิดจากข้อ จำกัด ชั่วคราวของระบบแคชมัลติซีพียูซึ่งทั้งหมดนี้สามารถห่อหุ้มอย่างปลอดภัยในวัตถุซิงโครไนซ์ระบบปฏิบัติการทั่วไปเช่นอินเท็กซ์ mutexes และตัวแปรเงื่อนไข C ++ ข้อเสนอ
ค่าใช้จ่ายในการห่อหุ้มนี้เป็นเพียงการลดลงของประสิทธิภาพหนึ่งนาทีเมื่อเทียบกับสิ่งที่การใช้คำสั่ง CPU เฉพาะที่ละเอียดยิ่งขึ้นสามารถบรรลุผลได้ในบางกรณี คำหลัก (หรือ
volatile#pragma dont-mess-with-that-variableสำหรับฉันในฐานะโปรแกรมเมอร์ระบบการดูแล) น่าจะเพียงพอที่จะบอกคอมไพเลอร์ให้หยุดการสั่งการเข้าถึงหน่วยความจำใหม่ รหัสที่เหมาะสมสามารถผลิตได้อย่างง่ายดายด้วย direct asm directives เพื่อโรยไดรเวอร์ระดับต่ำและรหัส OS พร้อมคำแนะนำเฉพาะ ad hoc CPU หากไม่มีความรู้อย่างลึกซึ้งเกี่ยวกับวิธีการทำงานของฮาร์ดแวร์พื้นฐาน (ระบบแคชหรืออินเตอร์เฟสบัส) คุณจะต้องเขียนโค้ดที่ไร้ประโยชน์ไม่มีประสิทธิภาพหรือผิดพลาดอยู่ดี

การปรับvolatileคำหลักและบ๊อบหนึ่งนาทีเป็นทุกคน แต่ลุงของโปรแกรมเมอร์ระดับต่ำที่ผ่านการฝึกหัดยากที่สุด แทนที่จะเป็นเช่นนั้นกลุ่มปรกติของ C ++ maths ก็มีการออกแบบวันภาคสนาม แต่ก็ยังมีสิ่งที่เป็นนามธรรมที่เข้าใจยากอีกทั้งยังมีแนวโน้มในการแก้ปัญหาการออกแบบโดยมองหาปัญหาที่ไม่มีอยู่จริง

เฉพาะเวลานี้การเปลี่ยนแปลงที่จำเป็นในการกำหนดแง่มุมพื้นฐานของ C เช่นกันเนื่องจาก "อุปสรรค" เหล่านี้จะต้องถูกสร้างขึ้นแม้ในรหัส C ระดับต่ำเพื่อให้ทำงานได้อย่างถูกต้อง เหนือสิ่งอื่นใดความเสียหายที่เกิดขึ้นในความหมายของการแสดงออกโดยไม่มีคำอธิบายหรือเหตุผลใด ๆ

โดยสรุปแล้วความจริงที่ว่าคอมไพเลอร์สามารถสร้างรหัสเครื่องที่สอดคล้องกันจากชิ้นส่วนที่ไร้สาระของ C นี้เป็นเพียงผลที่ตามมาจากวิธีที่พวก C ++ เผชิญกับความไม่สอดคล้องของระบบแคชของปลายปี 2000
มันสร้างความวุ่นวายอย่างมากในแง่มุมพื้นฐานหนึ่งของ C (นิยามนิพจน์) ดังนั้นโปรแกรมเมอร์ C ส่วนใหญ่ - ที่ไม่ให้คำด่าเกี่ยวกับระบบแคชและถูกต้อง - ตอนนี้ถูกบังคับให้พึ่งพาผู้เชี่ยวชาญเพื่ออธิบาย ความแตกต่างระหว่างและa = b() + c()a = b + c

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

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

ในความเห็นที่ได้รับข้อมูลของฉัน C มีอันตรายอยู่แล้วในสถานะ K&R วิวัฒนาการที่มีประโยชน์คือการเพิ่มมาตรการความปลอดภัยที่ใช้ร่วมกัน ตัวอย่างเช่นการใช้เครื่องมือวิเคราะห์โค้ดขั้นสูงนี้สเป็คบังคับให้คอมไพเลอร์นำไปใช้อย่างน้อยสร้างคำเตือนเกี่ยวกับรหัส bonkers แทนการสร้างรหัสเงียบ ๆ อาจไม่น่าเชื่อถือกับสุดขีด
แต่แทนที่จะเป็นคนตัดสินใจยกตัวอย่างเช่นเพื่อกำหนดลำดับการประเมินผลคงที่ใน C ++ 17 ขณะนี้ซอฟต์แวร์ทุกตัวถูกกระตุ้นอย่างแข็งขันเพื่อวางผลข้างเคียงในรหัสของเขา / เธอตามความเชื่อมั่นในความมั่นใจว่าคอมไพเลอร์ใหม่จะจัดการกับการทำให้งงงวยอย่างกระตือรือร้น

K&R เป็นหนึ่งในสิ่งมหัศจรรย์ของโลกแห่งคอมพิวเตอร์ สำหรับยี่สิบเหรียญคุณมีคุณสมบัติครบถ้วนของภาษา (ฉันเคยเห็นคนคนหนึ่งเขียนคอมไพเลอร์ฉบับสมบูรณ์โดยใช้หนังสือเล่มนี้) คู่มืออ้างอิงที่ยอดเยี่ยม (สารบัญมักจะชี้คุณภายในสองหน้าของคำตอบสำหรับคุณ คำถาม) และหนังสือเรียนที่จะสอนให้คุณใช้ภาษาอย่างสมเหตุสมผล สมบูรณ์ด้วยเหตุผลตัวอย่างและคำเตือนที่ชาญฉลาดเกี่ยวกับวิธีการมากมายที่คุณสามารถใช้ภาษาในการทำสิ่งที่โง่มาก

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


มันเป็นพฤติกรรมที่ไม่ได้กำหนดหากมีผลข้างเคียงกับวัตถุเดียวกันในนิพจน์เดียวกัน C17 6.5 / 2 สิ่งเหล่านี้ไม่ได้เกิดขึ้นตาม C17 6.5.18 / 3 แต่ข้อความจาก 6.5 / 2 "หากผลข้างเคียงของวัตถุสเกลาร์นั้นสัมพันธ์กันกับผลข้างเคียงที่แตกต่างกันในวัตถุสเกลาร์เดียวกันหรือการคำนวณมูลค่าโดยใช้ค่าของวัตถุสเกลาร์เดียวกันพฤติกรรมจะไม่ได้กำหนดไว้" ใช้ไม่ได้เนื่องจากการคำนวณค่าภายในฟังก์ชั่นถูกจัดลำดับทั้งก่อนหรือหลังการเข้าถึงดัชนีอาเรย์โดยไม่คำนึงถึงตัวดำเนินการกำหนดค่าที่มีตัวถูกดำเนินการตามลำดับ
Lundin

การเรียกใช้ฟังก์ชันทำหน้าที่เหมือนกับ "a mutex ต่อการเข้าถึงที่ไม่เกิด" หากคุณต้องการ 0,expr,0คล้ายกับการปิดบังจุลภาคอึประกอบการเช่น
Lundin

ฉันคิดว่าคุณเชื่อว่าผู้เขียนของมาตรฐานเมื่อพวกเขากล่าวว่า "พฤติกรรมที่ไม่ได้กำหนดให้ใบอนุญาต implementor ไม่ได้ที่จะจับข้อผิดพลาดโปรแกรมบางอย่างที่เป็นเรื่องยากที่จะวินิจฉัยนอกจากนี้ยังมีพื้นที่ที่ระบุของการขยายภาษาสอดคล้องเป็นไปได้:. implementor อาจเพิ่มภาษาโดยการให้ คำจำกัดความของพฤติกรรมที่ไม่ได้กำหนดอย่างเป็นทางการ " และกล่าวว่ามาตรฐานไม่ควรที่จะลดโปรแกรมที่มีประโยชน์ซึ่งไม่ได้ปฏิบัติตามอย่างเคร่งครัด ฉันคิดว่าผู้แต่งส่วนใหญ่ของ Standard จะคิดว่ามันชัดเจนว่าผู้ที่ต้องการเขียนคอมไพเลอร์คุณภาพ ...
supercat

... ควรพยายามใช้ UB เป็นโอกาสในการทำให้คอมไพเลอร์มีประโยชน์มากที่สุดสำหรับลูกค้าของพวกเขา ฉันสงสัยว่าอาจมีจินตนาการที่ผู้เขียนคอมไพเลอร์จะใช้เป็นข้ออ้างในการตอบข้อร้องเรียนของ "คอมไพเลอร์ของคุณประมวลผลโค้ดนี้มีประโยชน์น้อยกว่าคนอื่น ๆ " กับ "นั่นเป็นเพราะมาตรฐานไม่ต้องการให้เราดำเนินการอย่างเป็นประโยชน์ ที่ประมวลผลโปรแกรมที่มีประโยชน์ซึ่งพฤติกรรมไม่ได้รับคำสั่งจากมาตรฐานเพียงส่งเสริมการเขียนโปรแกรมที่เสียหาย "
supercat

ฉันไม่เห็นประเด็นในคำพูดของคุณ การใช้ลักษณะการทำงานเฉพาะของคอมไพเลอร์เป็นการรับประกันว่าไม่สามารถพกพาได้ นอกจากนี้ยังต้องใช้ความเชื่อมั่นอย่างยิ่งในผู้ผลิตคอมไพเลอร์ซึ่งสามารถหยุด "คำจำกัดความพิเศษ" เหล่านี้ได้ทุกเมื่อ สิ่งเดียวที่คอมไพเลอร์สามารถทำได้คือสร้างคำเตือนซึ่งโปรแกรมเมอร์ที่ชาญฉลาดและมีความรู้อาจตัดสินใจจัดการเช่นข้อผิดพลาด ปัญหาที่ฉันเห็นกับสัตว์ประหลาด ISO นี้ก็คือมันทำให้รหัสเลวร้ายเป็นตัวอย่างที่ถูกต้องของ OP (ด้วยเหตุผลที่ไม่ชัดเจนมากเมื่อเทียบกับคำนิยาม K&R ของการแสดงออก)
kuroi neko
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.