คำสั่ง“ goto” ของแมลงชนิดใดที่นำไปสู่ มีตัวอย่างที่สำคัญทางประวัติศาสตร์หรือไม่?


103

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

XKCD Alt Text: "Neal Stephenson คิดว่ามันน่ารักที่จะตั้งชื่อป้ายกำกับ 'dengo' ของเขา"
ดูการ์ตูนต้นฉบับที่: http://xkcd.com/292/

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

  • ความไม่แน่นอน?
  • รหัสที่ไม่สามารถเข้าใจได้หรืออ่านไม่ได้?
  • ช่องโหว่ด้านความปลอดภัย?
  • มีอะไรอีกบ้าง?

ข้อผิดพลาดประเภทใดที่ทำให้ข้อความ "goto" นำไปสู่ มีตัวอย่างที่สำคัญทางประวัติศาสตร์หรือไม่?


33
คุณอ่านบทความสั้น ๆ ของ Dijkstra ในหัวข้อนี้หรือไม่? (นั่นคือคำถามใช่ / ไม่ใช่และคำตอบอื่นใดนอกเหนือจากคำว่า "ใช่" คือ "ไม่")
John R. Strohm

2
ฉันสันนิษฐานว่าทางออกจากสัญญาณเตือนภัยคือเมื่อมีสิ่งผิดปกติเกิดขึ้น คุณจะไม่กลับมาจากไหน เช่นเดียวกับไฟฟ้าดับอุปกรณ์หรือไฟล์หายไป "on error goto" บางชนิด ... แต่ฉันคิดว่าข้อผิดพลาด "เกือบจะเสียหาย" ส่วนใหญ่สามารถจัดการได้โดยการสร้าง "try - catch" ในปัจจุบัน
Lenne

13
ไม่มีใครพูดถึง GOTO ที่คำนวณได้เลย ย้อนกลับไปตอนที่โลกยังเด็ก BASIC มีหมายเลขบรรทัด คุณสามารถเขียนสิ่งต่อไปนี้: 100 N = A * 100; GOTO N. ลองแก้จุดบกพร่องที่ :)
mcottle

7
@ JohnR.Strohm ฉันได้อ่านกระดาษของ Dijkstra มันถูกเขียนในปี 1968 และแสดงให้เห็นว่าความต้องการ gotos อาจลดลงในภาษาที่มีคุณสมบัติขั้นสูงเช่นคำสั่งสวิตช์และฟังก์ชั่น มันถูกเขียนขึ้นเพื่อตอบสนองต่อภาษาที่ goto เป็นวิธีการหลักในการควบคุมการไหลและสามารถใช้เพื่อข้ามไปยังจุดใดก็ได้ในโปรแกรม ในขณะที่ภาษาที่พัฒนาขึ้นหลังจากที่เขียนบทความนี้เช่น C, goto สามารถข้ามไปยังที่ต่างๆใน stack frame เดียวกันเท่านั้นและโดยทั่วไปจะใช้เมื่อตัวเลือกอื่นทำงานได้ไม่ดีเท่านั้น (ต่อ ... )
เรย์

10
(... ต่อ) คะแนนของเขาใช้ได้ในขณะนั้น แต่ไม่มีความเกี่ยวข้องอีกต่อไปไม่ว่า goto จะเป็นความคิดที่ดีหรือไม่ หากเราต้องการที่จะโต้แย้งว่าไม่ทางใดก็ทางหนึ่งจะต้องมีการโต้แย้งที่เกี่ยวข้องกับภาษาสมัยใหม่ คุณได้อ่าน "การเขียนโปรแกรมแบบโครงสร้างด้วยคำสั่ง goto" ของ Knuth หรือไม่?
เรย์

คำตอบ:


2

นี่เป็นวิธีที่จะดูว่าฉันยังไม่เห็นคำตอบอื่น ๆ

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

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

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


ข้ามไปที่ไม่ใช่ท้องถิ่น
Deduplicator

thebrain.mcgill.ca/flash/capsules/experience_jaune03.html << จิตใจมนุษย์สามารถติดตามสิ่งต่าง ๆ ได้ 7 รายการโดยทันที เหตุผลของคุณเล่นเป็นข้อ จำกัด นี้เนื่องจากขอบเขตการขยายจะเพิ่มสิ่งอื่น ๆ อีกมากมายที่ต้องติดตาม ดังนั้นประเภทของข้อบกพร่องที่จะนำมาใช้นั้นน่าจะเป็นการละเลยและหลงลืมไป นอกจากนี้คุณยังเสนอกลยุทธ์บรรเทาผลกระทบและแนวปฏิบัติที่ดีโดยทั่วไปในการ "รักษาขอบเขตของคุณให้แน่น" ฉันยอมรับคำตอบนี้ เก่งมากมาร์ติน
Akiva

1
มันเจ๋งแค่ไหน! มากกว่า 200 โหวตมีผู้ชนะเพียง 1!
Martin Maat

68

ไม่ใช่ว่าgotoเป็นสิ่งที่ไม่ดีโดยตัวของมันเอง (หลังจากนั้นทุกคำสั่งการกระโดดในคอมพิวเตอร์เป็น goto) ปัญหาคือมีรูปแบบการเขียนโปรแกรมของมนุษย์ที่มีการเขียนโปรแกรมที่มีโครงสร้างล่วงหน้าสิ่งที่อาจเรียกได้ว่าการเขียนโปรแกรม

ในการเขียนโปรแกรมโฟลว์ชาร์ต (ซึ่งคนในรุ่นของฉันเรียนรู้และใช้สำหรับโปรแกรม Apollo moon) คุณสร้างไดอะแกรมที่มีบล็อกสำหรับการประหารชีวิตและเพชรสำหรับการตัดสินใจ (เรียกว่า "รหัสสปาเก็ตตี้")

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

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

gotoแน่นอนคนยังสามารถเขียนโค้ดปาเก็ตตี้ได้โดยไม่ต้อง วิธีการทั่วไปคือการเขียนwhile(...) switch( iState ){...โดยที่กรณีที่แตกต่างกันตั้งค่าที่iStateแตกต่างกัน ในความเป็นจริงใน C หรือ C ++ เราสามารถเขียนแมโครเพื่อทำสิ่งนั้นและตั้งชื่อมันGOTOดังนั้นการบอกว่าคุณไม่ได้ใช้gotoก็คือความแตกต่างโดยไม่มีความแตกต่าง

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

ป้อนคำอธิบายรูปภาพที่นี่


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

23
@ MikeDunlavey: ที่เกี่ยวข้อง: "ระวังข้อบกพร่องในโค้ดข้างต้นฉันได้พิสูจน์แล้วว่าถูกต้องเท่านั้นไม่ได้ลองเลย" staff.fnwi.uva.nl/p.vanemdeboas/knuthnote.pdf
Mooing Duck

26
@ RobertHarvey ไม่เป็นความจริงเลยที่การพิสูจน์ความถูกต้องเป็นจริงสำหรับโปรแกรมที่ไม่สำคัญเท่านั้น อย่างไรก็ตามจำเป็นต้องใช้เครื่องมือพิเศษมากกว่าการเขียนโปรแกรมแบบมีโครงสร้าง
Mike Haskel

18
@MSalters: เป็นโครงการวิจัย เป้าหมายของโครงการวิจัยดังกล่าวคือแสดงให้เห็นว่าพวกเขาสามารถเขียนคอมไพเลอร์ที่ผ่านการตรวจสอบแล้วหากพวกเขามีทรัพยากร หากคุณดูที่งานปัจจุบันของคุณคุณจะเห็นว่าพวกเขาไม่สนใจที่จะสนับสนุน C รุ่นต่อมา แต่พวกเขาสนใจที่จะขยายคุณสมบัติความถูกต้องที่พวกเขาสามารถพิสูจน์ได้ และสำหรับสิ่งนั้นมันไม่เกี่ยวข้องอย่างสมบูรณ์เพียงว่าพวกเขาสนับสนุน C90, C99, C11, Pascal, Fortran, Algol หรือPlankalkül
Jörg W Mittag

8
@ Martineau: ฉันพูดเหลวไหลของ "วลี boson" ที่โปรแกรมเมอร์เรียงลำดับตกอยู่ในบรรทัดยอมรับว่ามันไม่ดีหรือดีเพราะพวกเขาจะนั่งบนถ้าพวกเขาไม่ จะต้องมีเหตุผลพื้นฐานเพิ่มเติมสำหรับสิ่งต่าง ๆ
Mike Dunlavey

63

เหตุใด goto จึงเป็นอันตราย

  • gotoไม่ก่อให้เกิดความไม่แน่นอนด้วยตัวเอง แม้จะมีประมาณ 100,000 gotos, เคอร์เนล Linuxยังคงเป็นรูปแบบของความมั่นคง
  • gotoด้วยตัวเองไม่ควรทำให้เกิดช่องโหว่ความปลอดภัย อย่างไรก็ตามในบางภาษาการผสมกับtry/ catchblock การจัดการข้อยกเว้นอาจทำให้เกิดช่องโหว่ตามที่อธิบายไว้ในคำแนะนำ CERTนี้ คอมไพเลอร์หลัก C ++ ตั้งค่าสถานะและป้องกันข้อผิดพลาดดังกล่าว แต่น่าเสียดายที่คอมไพเลอร์รุ่นเก่าหรือแปลกใหม่ไม่ได้ทำ
  • gotoทำให้โค้ดที่อ่านไม่ได้และไม่สามารถแก้ไขได้ นี่เรียกอีกอย่างว่ารหัสสปาเก็ตตี้เพราะเช่นเดียวกับในจานสปาเก็ตตี้มันเป็นเรื่องยากมากที่จะติดตามการไหลของการควบคุมเมื่อมี gotos มากเกินไป

แม้ว่าคุณจะจัดการเพื่อหลีกเลี่ยงรหัสสปาเก็ตตี้และถ้าคุณใช้เพียงไม่กี่ gotos พวกเขายังคงอำนวยความสะดวกเช่นข้อบกพร่องและการรั่วไหลของทรัพยากร:

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

ข้อมูลเพิ่มเติมและคำพูด:

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

  • James Gosling & Henry McGilton เขียนในกระดาษขาวภาษาจาวา 1995 สภาพแวดล้อมของพวกเขา:

    ไม่มีคำสั่งไปที่ใดเพิ่มเติม
    Java ไม่มีคำสั่ง goto การศึกษาแสดงให้เห็นว่าข้ามไปใช้ (mis) ใช้บ่อยกว่าไม่เพียง "เพราะมันมี" การกำจัด goto นำไปสู่ความเรียบง่ายของภาษา (... ) การศึกษาเกี่ยวกับรหัส C ประมาณ 100,000 บรรทัดระบุว่าคำสั่ง goto ประมาณ 90 เปอร์เซ็นต์ถูกนำมาใช้อย่างหมดจดเพื่อให้ได้ผลของการแบ่งลูปซ้อนกัน ดังที่ได้กล่าวมาแล้วการแบ่งหลายระดับและลบส่วนใหญ่ที่จำเป็นสำหรับคำสั่ง goto ออกไป

  • Bjarne Stroustrupกำหนด goto ในอภิธานศัพท์ของเขาในคำเชิญเหล่านี้:

    goto - goto ที่น่าอับอาย ส่วนใหญ่มีประโยชน์ในเครื่องสร้างรหัส C ++

เมื่อไหร่ที่จะสามารถใช้ goto ได้?

เช่นเดียวกับ K&R ฉันไม่เชื่อเรื่อง gotos ฉันยอมรับว่ามีสถานการณ์ที่ข้ามไปอาจทำให้ชีวิตของคน ๆ หนึ่งสงบลงได้

โดยทั่วไปใน C, goto อนุญาตให้ออกจากลูปหลายระดับหรือการจัดการข้อผิดพลาดที่ต้องไปถึงจุดออกที่เหมาะสมที่ปลดปล่อย / ปลดล็อกทรัพยากรทั้งหมดที่ได้รับการจัดสรรจนถึงขณะนี้ (การจัดสรร iemultiple ตามลำดับหมายถึงฉลากหลายรายการ) บทความนี้แสดงปริมาณการใช้ goto ที่แตกต่างกันในเคอร์เนล Linux

ส่วนตัวฉันชอบที่จะหลีกเลี่ยงมันและใน 10 ปีของ C ฉันใช้ 10 gotos สูงสุด ฉันชอบใช้งานซ้อนกันifซึ่งฉันคิดว่าอ่านง่ายขึ้น เมื่อสิ่งนี้นำไปสู่การทำรังที่ลึกเกินไปฉันจะเลือกที่จะย่อยสลายฟังก์ชั่นของฉันในส่วนเล็ก ๆ หรือใช้ตัวบ่งชี้บูลีนในน้ำตก gotoวันนี้คอมไพเลอร์เพิ่มประสิทธิภาพจะฉลาดพอที่จะสร้างเกือบรหัสเดียวกันกว่ารหัสเดียวกันกับ

การใช้ goto อย่างหนักขึ้นอยู่กับภาษา:

  • ใน C ++ การใช้ที่เหมาะสมของRAIIจะทำให้คอมไพเลอร์ทำลายวัตถุที่อยู่นอกขอบเขตโดยอัตโนมัติดังนั้นทรัพยากร / ล็อคจะถูกล้างอีกต่อไปและไม่จำเป็นต้องกลับไปใช้อีกต่อไป

  • ใน Java มีความจำเป็นสำหรับการกลับไปข้าง (ดูของ Java ผู้เขียนอ้างข้างต้นและที่ยอดเยี่ยมคำตอบกองมากเกิน ): เก็บขยะที่ทำความสะอาดระเบียบbreak, continueและtry/ catchจัดการข้อยกเว้นครอบคลุมทุกกรณีที่gotoอาจจะเป็นประโยชน์ แต่ในความปลอดภัยมากขึ้นและดีขึ้น ลักษณะ. ความนิยมของ Java พิสูจน์ได้ว่าคำสั่ง goto สามารถหลีกเลี่ยงได้ในภาษาสมัยใหม่

ขยายช่องโหว่SSL ที่มีชื่อเสียงออกไปไม่ได้

ข้อจำกัดความรับผิดชอบที่สำคัญ:ในมุมมองของการสนทนาที่รุนแรงในความคิดเห็นที่ฉันต้องการชี้แจงว่าฉันไม่ได้แกล้งทำเป็นว่าคำสั่ง goto เป็นเพียงสาเหตุของข้อผิดพลาดนี้ ฉันไม่แสร้งว่าไม่มีถ้าข้ามไปแล้วจะไม่มีข้อผิดพลาด ฉันแค่ต้องการแสดงให้เห็นว่ามีข้อผิดพลาดร้ายแรงสามารถมีส่วนร่วมในข้อผิดพลาดร้ายแรง

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

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

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

ข้อผิดพลาดอยู่ในฟังก์ชั่นSSLEncodeSignedServerKeyExchange()ในไฟล์นี้ :

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) != 0)
        goto fail;
    if ((err =...) !=0)
        goto fail;
    if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
        goto fail;
        goto fail;  // <====OUCH: INDENTATION MISLEADS: THIS IS UNCONDITIONDAL!!
    if (...)
        goto fail;

    ... // Do some cryptographic operations here

fail:
    ... // Free resources to process error

วงเล็บปีกกาที่แน่นอนรอบ ๆ บล็อกเงื่อนไขสามารถป้องกันข้อผิดพลาด:
มันจะนำไปสู่ข้อผิดพลาดทางไวยากรณ์ที่รวบรวม (และด้วยเหตุนี้การแก้ไข) หรือไปที่ไม่เป็นอันตรายซ้ำซ้อน อย่างไรก็ตาม GCC 6 จะสามารถตรวจพบข้อผิดพลาดเหล่านี้ได้เนื่องจากคำเตือนเพิ่มเติมเพื่อตรวจหาการเยื้องที่ไม่สอดคล้องกัน

แต่ในตอนแรก gotos ทั้งหมดสามารถหลีกเลี่ยงได้ด้วยโค้ดที่มีโครงสร้างมากขึ้น ดังนั้นอย่างน้อยก็เป็นสาเหตุของข้อผิดพลาดนี้ทางอ้อม มีอย่างน้อยสองวิธีที่สามารถหลีกเลี่ยงได้:

วิธีที่ 1: ถ้าประโยคหรือซ้อนกันifs

แทนที่จะทำการทดสอบเงื่อนไขจำนวนมากเพื่อหาข้อผิดพลาดตามลำดับและแต่ละครั้งที่ส่งไปยังfailป้ายกำกับในกรณีที่เกิดปัญหาเราสามารถเลือกที่จะดำเนินการเข้ารหัสลับในสถานะifที่ระบุว่าจะทำได้เฉพาะเมื่อไม่มีเงื่อนไขผิดปกติ:

    if ((err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0 &&
        (err = ...) == 0 ) &&
        (err = ReadyHash(&SSLHashSHA1, &hashCtx)) == 0) &&
        ...
        (err = ...) == 0 ) )
    {
         ... // Do some cryptographic operations here
    }
    ... // Free resources

วิธีที่ 2: ใช้ตัวสะสมข้อผิดพลาด

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

bool ok = true;
ok =  ok && (err = ReadyHash(&SSLHashSHA1, &hashCtx))) == 0;
ok =  ok && (err = NextFunction(...)) == 0;
...
ok =  ok && (err = ...) == 0;
... // Free resources

ที่นี่ไม่มีการข้ามไปครั้งเดียว: ไม่มีความเสี่ยงที่จะข้ามไปยังจุดออกความล้มเหลวอย่างรวดเร็ว และจะทำให้มองเห็นเส้นที่ไม่ถูกต้องหรือเส้นที่ลืมok &&ได้ง่าย

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

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

assert( (ok==false && err!=0) || (ok==true && err==0) );

ข้อผิดพลาดของการ==0แทนที่โดยไม่ตั้งใจด้วย!=0ข้อผิดพลาดหรือตัวเชื่อมต่อแบบลอจิคัลจะถูกตรวจพบได้ง่ายในระหว่างขั้นตอนการดีบัก

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


34
ไม่ข้อผิดพลาดข้ามไปของ Apple ล้มเหลวเกิดจากโปรแกรมเมอร์ที่ฉลาดตัดสินใจไม่รวมวงเล็บปีกกาหลังคำสั่ง if :-) ดังนั้นเมื่อโปรแกรมเมอร์บำรุงรักษาคนต่อไป (หรือคนที่เหมือนกันมีความแตกต่างเหมือนกัน) เข้ามาและเพิ่มอีกบรรทัดของรหัสที่ควรจะดำเนินการเฉพาะเมื่อคำสั่ง if ประเมินผลจริงคำสั่งจะทำงานทุกครั้ง Bam ข้อผิดพลาด "goto fail" ซึ่งเป็นข้อผิดพลาดด้านความปลอดภัยที่สำคัญ แต่ก็ไม่มีอะไรเกี่ยวข้องกับข้อเท็จจริงที่ว่ามีการใช้คำสั่ง goto
Craig

47
ข้อผิดพลาด "ข้ามไปล้มเหลว" ของ Apple เกิดขึ้นโดยตรงเนื่องจากมีรหัสบรรทัดซ้ำกัน โดยเฉพาะอย่างยิ่งผลกระทบของข้อผิดพลาดที่เกิดจากเส้นที่เป็นgotoภายในรั้งน้อยifคำสั่ง แต่ถ้าคุณแนะนำว่าการหลีกเลี่ยงgotoจะทำให้โค้ดของคุณรอดพ้นจากผลกระทบของบรรทัดที่ซ้ำกันแบบสุ่มฉันมีข่าวดีสำหรับคุณ
Immibis

12
คุณกำลังใช้ตัวดำเนินการบูลีนช็อตคัตเพื่อเรียกใช้คำสั่งผ่านผลข้างเคียงและอ้างว่าชัดเจนกว่าifคำสั่งใช่หรือไม่ ช่วงเวลาแห่งความสนุก. :)
Craig

38
หากแทนที่จะเป็น "goto Fail" จะมีข้อความว่า "failed = true" สิ่งเดียวกันนี้จะเกิดขึ้น
gnasher729

15
ข้อผิดพลาดนั้นเกิดจากนักพัฒนาที่เลอะเทอะไม่ใส่ใจกับสิ่งที่เขาทำและไม่อ่านการเปลี่ยนแปลงรหัสของตนเอง ไม่มีอะไรเพิ่มเติมไม่น้อยไปกว่านี้ โอเคอาจจะโยนในการทดสอบการกำกับดูแลบางส่วนที่นั่นเช่นกัน
การแข่งขัน Lightness ใน Orbit

34

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

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

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

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

ป้อนคำอธิบายรูปภาพที่นี่

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

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


2
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
maple_shaft

11

คำสั่ง“ goto” ของแมลงชนิดใดที่นำไปสู่ มีตัวอย่างที่สำคัญทางประวัติศาสตร์หรือไม่?

ฉันเคยใช้gotoคำสั่งเมื่อเขียนโปรแกรม BASIC เป็นเด็กเป็นวิธีง่ายๆในการรับและในขณะที่ลูป (Commodore 64 พื้นฐานไม่ได้มีลูปในขณะที่และฉันก็ยังอ่อนเกินไปที่จะเรียนรู้ไวยากรณ์ที่เหมาะสมและการใช้ for-loops ) รหัสของฉันค่อนข้างบ่อย แต่มีข้อผิดพลาดวนซ้ำอาจเกิดจากการใช้งาน goto ทันที

ตอนนี้ฉันใช้ Python ซึ่งเป็นภาษาโปรแกรมระดับสูงที่กำหนดแล้วว่าไม่จำเป็นต้องใช้ข้ามไปเลย

เมื่อ Edsger Dijkstra ประกาศว่า "ไปถือว่าอันตราย" ในปี 1968 เขาไม่ได้ให้กำมือของตัวอย่างที่ข้อบกพร่องที่เกี่ยวข้องอาจจะมีตำหนิที่ข้ามไปได้ค่อนข้างเขาบอกว่าgotoเป็นที่ไม่จำเป็นสำหรับภาษาระดับที่สูงขึ้นและที่ควรหลีกเลี่ยงในความโปรดปรานของสิ่งที่ เราพิจารณาการควบคุมการไหลปกติวันนี้: ลูปและเงื่อนไข คำพูดของเขา:

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

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

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

ตัวอย่างที่ไม่มีหลักฐาน

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

ตัวอย่างที่เคาน์เตอร์

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

สำหรับบันทึกฉันพบว่าตัวนับตัวอย่างนี้ง่ายต่อการวิเคราะห์แบบคงที่


5
"เรื่องราวของเมล" ที่ฉันพบคือสถาปัตยกรรมแปลก ๆ ที่ (1) การเรียนการสอนภาษาเครื่องทุกครั้งจะข้ามไปหลังจากการดำเนินการและ (2) มันไม่มีประสิทธิภาพมากนักในการวางลำดับของคำสั่งในตำแหน่งหน่วยความจำต่อเนื่อง และโปรแกรมนั้นเขียนด้วยชุดประกอบที่ปรับปรุงด้วยมือไม่ใช่ภาษาที่คอมไพล์

@MilesRout ฉันคิดว่าคุณสามารถแก้ไขได้ที่นี่ แต่หน้าวิกิพีเดียในการเขียนโปรแกรมแบบมีโครงสร้างกล่าวว่า: "ส่วนเบี่ยงเบนที่พบบ่อยที่สุดที่พบในหลายภาษาคือการใช้คำสั่ง return สำหรับการออกจากรูทีนย่อย แทนที่จะเป็นจุดออกเดียวที่ต้องการโดยการเขียนโปรแกรมแบบมีโครงสร้าง " - คุณกำลังบอกว่านี่ไม่ถูกต้อง? คุณมีแหล่งอ้างอิงหรือไม่?
Aaron Hall

@AaronHall นั่นไม่ใช่ความหมายดั้งเดิม
เส้นทาง Miles

11

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

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

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

คุณแก่พอที่จะรู้วลี"เป็นคำสั่งตัวเลข"หรือไม่? คุณไม่จำเป็นต้องรู้ แต่ถ้าคุณไม่เข้าใจคุณจะไม่คุ้นเคยกับบริบททางประวัติศาสตร์ที่คำเตือนgotoมีความเกี่ยวข้องเป็นหลัก

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

แต่gotoคำสั่งนี้ไม่สามารถถูกทำร้ายได้หรือไม่?

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

คุณอาจสังเกตเห็นขาดล่าสุดเรื่องราวสยองขวัญที่gotoเล่นเป็นนางร้าย เรื่องราวเหล่านี้ส่วนใหญ่หรือทั้งหมดดูเหมือนจะมีอายุ 30 หรือ 40 ปี คุณยืนอยู่บนพื้นแข็งถ้าคุณคิดว่าเรื่องราวเหล่านั้นล้าสมัยเป็นส่วนใหญ่


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

1
โพสต์ของคุณแย่มากฉันไม่สามารถลงคะแนนความคิดเห็นได้
Pieter B

7

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


1
Visual Basic 6 และ VBA มีการจัดการข้อผิดพลาดที่เจ็บปวด แต่ถ้าคุณใช้On Error Goto errhคุณสามารถใช้Resumeเพื่อลองResume Nextข้ามบรรทัดที่ละเมิดและErlเพื่อทราบว่าเป็นบรรทัดใด (ถ้าคุณใช้หมายเลขบรรทัด)
Cees Timmerman

@CeesTimmerman ฉันทำมาน้อยมากกับ Visual Basic ฉันไม่ทราบว่า ฉันจะเพิ่มความคิดเห็นเกี่ยวกับมัน
qfwfq

2
@CeesTimmerman: ซีแมนทิกส์ VB6 ของ "ประวัติย่อ" นั้นแย่มากหากเกิดข้อผิดพลาดภายในรูทีนย่อยที่ไม่มีบล็อกข้อผิดพลาดของตัวเอง Resumeจะเรียกใช้ฟังก์ชันนั้นอีกครั้งตั้งแต่เริ่มต้นและResume Nextจะข้ามทุกอย่างในฟังก์ชั่นที่ยังไม่ได้
supercat

2
@supercat อย่างที่ฉันพูดมันเจ็บปวด หากคุณจำเป็นต้องรู้บรรทัดที่แน่นอนคุณควรกำหนดหมายเลขทั้งหมดหรือปิดใช้งานการจัดการข้อผิดพลาดด้วยOn Error Goto 0ตนเองและดีบักด้วยตนเอง Resumeใช้เพื่อตรวจสอบErr.Numberและทำการปรับเปลี่ยนที่จำเป็น
Cees Timmerman

1
ฉันควรทราบว่าในภาษาที่ทันสมัยgotoใช้งานได้เฉพาะกับสายที่มีข้อความซึ่งปรากฏว่าได้รับการแทนที่โดยลูปที่มีป้ายกำกับ
Cees Timmerman

7

goto ยากกว่าที่มนุษย์จะให้เหตุผลมากกว่าการควบคุมการไหลในรูปแบบอื่น

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

การรับโค้ดเพื่อทำสิ่งที่คุณต้องการอย่างง่ายดายนั้นง่ายเมื่อเทียบกับทุกอย่างเกี่ยวกับการเขียนโปรแกรม

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

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

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

"ตื้น" coroutines ในภาษาที่ไม่มีการสนับสนุน coroutine (เช่น C ++ pre-C ++ 20 และ C) เป็นไปได้โดยใช้ส่วนผสมของ gotos และการจัดการสถานะด้วยตนเอง "Deep" coroutines สามารถทำได้โดยใช้setjmpและlongjmpหน้าที่ของ C

มีหลายกรณีที่ coroutines มีประโยชน์ดังนั้นการเขียนด้วยตนเองและอย่างรอบคอบมีค่า

ในกรณีของ C ++ พวกเขาพบว่ามีประโยชน์พอที่พวกเขาจะขยายภาษาเพื่อรองรับพวกเขา การgotoจัดการสถานะด้วยตนเองและแบบแมนนวลถูกซ่อนอยู่หลังเลเยอร์นามธรรมที่ไม่มีค่าใช้จ่ายอนุญาตให้โปรแกรมเมอร์เขียนโดยไม่ต้องพิสูจน์ความยุ่งเหยิงของ goto, void**s, การสร้างด้วยตนเอง / การทำลายของรัฐ ฯลฯ ถูกต้อง

ข้ามไปรับที่ซ่อนอยู่หลังนามธรรมระดับที่สูงขึ้นเช่นwhileหรือforหรือหรือif switchabstractions ระดับที่สูงขึ้นนั้นง่ายต่อการพิสูจน์ความถูกต้องและตรวจสอบ

หากภาษาหายไปบางส่วนของพวกเขา (เช่นภาษาที่ทันสมัยบางอย่างหายไป coroutines) การ จำกัด พร้อมกับรูปแบบที่ไม่เหมาะกับปัญหาหรือใช้ goto กลายเป็นทางเลือกของคุณ

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


1
longjmpถูกกฎหมายใน C เท่านั้นสำหรับการคลายสแต็กกลับเป็น a setjmpในฟังก์ชันพาเรนต์ (เช่นแยกออกจากการซ้อนหลายระดับ แต่สำหรับการเรียกใช้ฟังก์ชันแทนการวนซ้ำ) longjjmpจากบริบทที่setjmpทำในฟังก์ชั่นที่ส่งคืนเนื่องจากไม่ถูกต้องตามกฎหมาย (และฉันสงสัยว่าจะไม่ทำงานในทางปฏิบัติในกรณีส่วนใหญ่) ฉันไม่คุ้นเคยกับ co-routines แต่จากคำอธิบายของคุณเกี่ยวกับ co-rout คล้ายกับ thread-space ของผู้ใช้ฉันคิดว่ามันจะต้องใช้ stack ของตัวเองเช่นเดียวกับบริบทที่บันทึกไว้และlongjmpให้คุณลงทะเบียนเท่านั้น - ประหยัดไม่ใช่สแต็กแยกต่างหาก
Peter Cordes

1
โอ้ฉันควรจะมี googled: fanf.livejournal.com/105413.htmlอธิบายถึงการสร้างพื้นที่สแต็คเพื่อให้ coroutine ทำงาน (ซึ่งฉันสงสัยว่าเป็นสิ่งจำเป็นด้านบนของเพียงแค่ใช้ setjmp / longjmp)
Peter Cordes

2
@PeterCordes: การใช้งานไม่จำเป็นต้องมีวิธีการใด ๆ ที่รหัสสามารถสร้างคู่ jmp_buff ที่เหมาะสำหรับใช้เป็น coroutine การใช้งานบางอย่างระบุวิธีการที่รหัสอาจสร้างหนึ่งแม้ว่ามาตรฐานไม่ต้องการให้มีความสามารถดังกล่าว ฉันจะไม่อธิบายรหัสที่อาศัยเทคนิคเช่น "มาตรฐาน C" เนื่องจากในหลาย ๆ กรณี jmp_buff จะเป็นโครงสร้างทึบแสงที่ไม่รับประกันว่าจะทำงานอย่างสม่ำเสมอระหว่างคอมไพเลอร์ที่แตกต่างกัน
supercat

6

ลองดูรหัสนี้จากhttp://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.htmlซึ่งเป็นส่วนหนึ่งของการจำลอง Dilemma ของนักโทษขนาดใหญ่ หากคุณเห็นรหัส FORTRAN หรือ BASIC เก่าคุณจะรู้ว่าไม่ใช่เรื่องแปลก

C  Not nice rules in second round of tour (cut and pasted 7/15/93)
   FUNCTION K75R(J,M,K,L,R,JA)
C  BY P D HARRINGTON
C  TYPED BY JM 3/20/79
   DIMENSION HIST(4,2),ROW(4),COL(2),ID(2)
   K75R=JA       ! Added 7/32/93 to report own old value
   IF (M .EQ. 2) GOTO 25
   IF (M .GT. 1) GOTO 10
   DO 5 IA = 1,4
     DO 5 IB = 1,2
5  HIST(IA,IB) = 0

   IBURN = 0
   ID(1) = 0
   ID(2) = 0
   IDEF = 0
   ITWIN = 0
   ISTRNG = 0
   ICOOP = 0
   ITRY = 0
   IRDCHK = 0
   IRAND = 0
   IPARTY = 1
   IND = 0
   MY = 0
   INDEF = 5
   IOPP = 0
   PROB = .2
   K75R = 0
   RETURN

10 IF (IRAND .EQ. 1) GOTO 70
   IOPP = IOPP + J
   HIST(IND,J+1) = HIST(IND,J+1) + 1
   IF (M .EQ. 15 .OR. MOD(M,15) .NE. 0 .OR. IRAND .EQ. 2) GOTO 25
   IF (HIST(1,1) / (M - 2) .GE. .8) GOTO 25
   IF (IOPP * 4 .LT. M - 2 .OR. IOPP * 4 .GT. 3 * M - 6) GOTO 25
   DO 12 IA = 1,4
12 ROW(IA) = HIST(IA,1) + HIST(IA,2)

   DO 14 IB = 1,2
     SUM = .0
     DO 13 IA = 1,4
13   SUM = SUM + HIST(IA,IB)
14 COL(IB) = SUM

   SUM = .0
   DO 16 IA = 1,4
     DO 16 IB = 1,2
       EX = ROW(IA) * COL(IB) / (M - 2)
       IF (EX .LE. 1.) GOTO 16
       SUM = SUM + ((HIST(IA,IB) - EX) ** 2) / EX
16 CONTINUE

   IF (SUM .GT. 3) GOTO 25
   IRAND = 1
   K75R = 1
   RETURN

25 IF (ITRY .EQ. 1 .AND. J .EQ. 1) IBURN = 1
   IF (M .LE. 37 .AND. J .EQ. 0) ITWIN = ITWIN + 1
   IF (M .EQ. 38 .AND. J .EQ. 1) ITWIN = ITWIN + 1
   IF (M .GE. 39 .AND. ITWIN .EQ. 37 .AND. J .EQ. 1) ITWIN = 0
   IF (ITWIN .EQ. 37) GOTO 80
   IDEF = IDEF * J + J
   IF (IDEF .GE. 20) GOTO 90
   IPARTY = 3 - IPARTY
   ID(IPARTY) = ID(IPARTY) * J + J
   IF (ID(IPARTY) .GE. INDEF) GOTO 78
   IF (ICOOP .GE. 1) GOTO 80
   IF (M .LT. 37 .OR. IBURN .EQ. 1) GOTO 34
   IF (M .EQ. 37) GOTO 32
   IF (R .GT. PROB) GOTO 34
32 ITRY = 2
   ICOOP = 2
   PROB = PROB + .05
   GOTO 92

34 IF (J .EQ. 0) GOTO 80
   GOTO 90

70 IRDCHK = IRDCHK + J * 4 - 3
   IF (IRDCHK .GE. 11) GOTO 75
   K75R = 1
   RETURN

75 IRAND = 2
   ICOOP = 2
   K75R = 0
   RETURN

78 ID(IPARTY) = 0
   ISTRNG = ISTRNG + 1
   IF (ISTRNG .EQ. 8) INDEF = 3
80 K75R = 0
   ITRY = ITRY - 1
   ICOOP = ICOOP - 1
   GOTO 95

90 ID(IPARTY) = ID(IPARTY) + 1
92 K75R = 1
95 IND = 2 * MY + J + 1
   MY = K75R
   RETURN
   END

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


4
จริง :-) อาจจะคุ้มค่าที่จะพูดถึง: มรดก FORTAN และพื้นฐานไม่ได้มีโครงสร้างบล็อกดังนั้นถ้าประโยคแล้วไม่สามารถถือในหนึ่งบรรทัด GOTO เป็นทางเลือกเดียว
Christophe

ป้ายข้อความแทนตัวเลขหรืออย่างน้อยความคิดเห็นในบรรทัดตัวเลขจะช่วยได้มาก
Cees Timmerman

ย้อนกลับไปในวัน FORTRAN-IV ของฉัน: ฉัน จำกัด การใช้ GOTO เพื่อใช้บล็อก IF / ELSE IF / ELSE ในขณะที่ลูปและ BREAK และ NEXT เป็นลูป บางคนแสดงให้ฉันเห็นถึงความเลวร้ายของรหัสสปาเก็ตตี้และทำไมคุณควรจัดโครงสร้างเพื่อหลีกเลี่ยงแม้ว่าคุณจะต้องนำโครงสร้างบล็อกของคุณไปใช้กับ GOTO ต่อมาฉันเริ่มใช้ตัวประมวลผลล่วงหน้า (RATFOR, IIRC) ไปที่ไม่ได้ชั่วร้ายภายในมันเป็นเพียงการสร้างระดับต่ำที่มีประสิทธิภาพมากกว่าที่แมปกับคำสั่งสาขาแอสเซมเบลอร์ หากคุณต้องใช้เพราะภาษาของคุณไม่เพียงพอให้ใช้มันเพื่อสร้างหรือเพิ่มโครงสร้างบล็อกที่เหมาะสม
nigel222

@CeesTimmerman: นั่นคือรหัส FORTRAN IV ในสวนที่หลากหลาย ไม่รองรับเลเบลข้อความใน FORTRAN IV ฉันไม่ทราบว่าภาษารุ่นล่าสุดสนับสนุนพวกเขาหรือไม่ ความคิดเห็นในบรรทัดเดียวกับรหัสไม่ได้รับการสนับสนุนใน FORTRAN IV มาตรฐานแม้ว่าอาจมีส่วนขยายเฉพาะของผู้ขายเพื่อสนับสนุนพวกเขา ฉันไม่ทราบว่ามาตรฐาน FORTRAN "ปัจจุบัน" กล่าวว่า: ฉันละทิ้ง FORTRAN นานแล้วและฉันไม่ควรพลาด (รหัส FORTRAN ล่าสุดที่ฉันเคยเห็นมีความคล้ายคลึงกับ PASCAL ในช่วงต้นทศวรรษ 1970)
John R. Strohm

1
@CeesTimmerman: ส่วนขยายเฉพาะของผู้ขาย รหัสโดยทั่วไปคืออายจริงๆในความคิดเห็น นอกจากนี้ยังมีการประชุมที่กลายเป็นเรื่องธรรมดาในบางสถานที่โดยการใส่หมายเลขบรรทัดในคำสั่ง CONTINUE และ FORMAT เท่านั้นเพื่อให้ง่ายต่อการเพิ่มบรรทัดในภายหลัง รหัสนี้ไม่เป็นไปตามแบบแผนนั้น
John R. Strohm

0

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

ภายในโค้ดหน่วยเดียวจะใช้หลักการเดียวกันนี้ หากคุณใช้การเขียนโปรแกรมเชิงโครงสร้างหรือหลักการเขียนโปรแกรมเชิงวัตถุคุณจะไม่:

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

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

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


3
คุณตอบโดยไม่พูดถึงgotoแม้แต่ครั้งเดียว :)
Robert Harvey

0

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


เหตุผลที่ใช้ / ไม่ใช้goto :

  • หากคุณจำเป็นต้องห่วงคุณต้องใช้หรือwhilefor

  • หากคุณต้องการกระโดดแบบมีเงื่อนไขให้ใช้ if/then/else

  • หากคุณต้องการโพรซีเดอร์ให้เรียกใช้ฟังก์ชัน / เมธอด

  • returnหากคุณต้องการที่จะเลิกฟังก์ชั่นเพียง

ฉันสามารถวางใจในสถานที่ที่ฉันเห็นว่าgotoใช้และใช้งานได้อย่างเหมาะสม

  • CPython
  • libKTX
  • อาจอีกไม่กี่

ใน libKTX มีฟังก์ชั่นที่มีรหัสต่อไปนี้

if(something)
    goto cleanup;

if(bla)
    goto cleanup;

cleanup:
    delete [] array;

ตอนนี้ที่gotoนี่มีประโยชน์เพราะภาษาคือ C:

  • เราอยู่ในฟังก์ชั่น
  • เราไม่สามารถเขียนฟังก์ชันการล้างข้อมูลได้ (เพราะเราเข้าสู่ขอบเขตอื่นและการทำให้ฟังก์ชั่นผู้โทรเข้าสามารถเข้าถึงได้เป็นภาระมากกว่า)

กรณีการใช้งานนี้จะเป็นประโยชน์เพราะใน C gotoเราไม่ได้มีการเรียนดังนั้นวิธีที่ง่ายที่สุดในการทำความสะอาดใช้

หากเรามีรหัสเดียวกันใน C ++ ไม่จำเป็นต้องมี goto อีกต่อไป:

class MyClass{
    public:

    void Function(){
        if(something)
            return Cleanup(); // invalid syntax in C#, but valid in C++
        if(bla)
            return Cleanup(); // invalid syntax in C#, but valid in C++
    }

    // access same members, no need to pass state (compiler do it for us).
    void Cleanup(){

    }



}

มันเป็นแมลงแบบไหนกัน? สิ่งใด ลูปไม่มีที่สิ้นสุดลำดับของการดำเนินการผิดพลาด

เอกสารที่มีเอกสารเป็นช่องโหว่ใน SSL ที่อนุญาตให้มนุษย์อยู่ในการโจมตีระดับกลางที่เกิดจากการใช้งานผิดไปจาก goto:นี่คือบทความ

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


2
มีความคิดเห็นมากมายสำหรับคำตอบเดียวที่อธิบายอย่างชัดเจนว่าการใช้ "goto" นั้นไม่เกี่ยวข้องกับข้อผิดพลาด SSL ทั้งหมด - ข้อผิดพลาดเกิดจากการทำสำเนารหัสบรรทัดเดียวอย่างไม่ระมัดระวังดังนั้นจึงถูกดำเนินการตามเงื่อนไขและครั้งเดียวโดยไม่มีเงื่อนไข
gnasher729

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

1
ตัวอย่างจากรหัสที่ฉันทำงานก่อนหน้านี้ใน PL / 1 CICS โปรแกรมนำหน้าจอออกมาประกอบด้วยแผนที่เส้นเดียว เมื่อหน้าจอถูกส่งคืนก็จะพบอาร์เรย์ของแผนที่ที่ส่งไป ใช้องค์ประกอบของสิ่งนั้นเพื่อค้นหาดัชนีในหนึ่งอาเรย์แล้วใช้ดัชนีนั้นไปยังดัชนีของตัวแปรอาเรย์เพื่อทำ GOTO เพื่อให้สามารถประมวลผลแต่ละแผนที่นั้นได้ หากอาเรย์ของแผนที่ที่ส่งไม่ถูกต้อง (หรือเสียหาย) ก็สามารถลอง GOTO ไปยังป้ายกำกับที่ไม่ถูกต้องหรือเกิดความผิดพลาดที่แย่ลงเพราะเข้าถึงองค์ประกอบหลังจากสิ้นสุดอาร์เรย์ของตัวแปรป้ายกำกับ เขียนซ้ำเพื่อใช้ไวยากรณ์ประเภทเคส
Kickstart
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.