โปรแกรมนี้จะยุติสำหรับ Integer ทุกคนหรือไม่


14

ในการทดสอบชิ้นส่วนสำหรับการเตรียม GATE มีคำถาม:

f(n):
     if n is even: f(n) = n/2
     else f(n) = f(f(n-1))

ผมตอบว่า "มันจะยุติสำหรับจำนวนเต็มทุกคน" เพราะแม้สำหรับจำนวนเต็มเชิงลบบางอย่างก็จะยุติการเป็นข้อผิดพลาดกองมากเกิน

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

คำตอบใดถูกต้องและทำไม


8
ไม่สิ้นสุดสำหรับ n = -1 ข้อ จำกัด ทางทฤษฎีส่วนใหญ่ได้รับการพิจารณาในกรณีเช่นนี้
Deep Joshi

9
หาก stack overflow นั้นถูกพิจารณาว่าเป็นการยุติโปรแกรมทั้งหมดจะยุติและจะเอาชนะวัตถุประสงค์ของคำถามนี้ ...
xuq01

10
@ xuq01 while (true);จะไม่ยุติและไม่ทำให้เกิดการโอเวอร์โฟลว์
TripeHound

3
@leftaroundabout ฉันอาจจะไม่ได้ใช้ " ในสิ่งที่เหมาะสม " เพราะมันเป็นระดับที่แตกต่างกันอย่างสิ้นเชิงของ " สมเหตุสมผล " ... จำและการดำเนินการเรียกซ้ำหางดี (หรือแม้กระทั่งที่เหมาะสม ) แต่ไม่ทำเช่นนั้นเป็นเพียงเล็กน้อย " ไม่เหมาะสม " สิ่งที่นำมาใช้while(true);ในทางที่ใช้ใด ๆสแต็คแน่นอนที่สุดจะไม่เหมาะสม ประเด็นคือถ้าคุณตั้งใจออกนอกลู่นอกทางwhile(true);จะไม่ยุติหรือทริกเกอร์ล้นสแต็ก
TripeHound

14
@ xuq01 ฉันไม่คิดว่า "การทำลายล้างจักรวาล" นับเป็นวิธีแก้ปัญหาการหยุดชะงัก
TripeHound

คำตอบ:


49

คำตอบที่ถูกต้องคือฟังก์ชั่นนี้ไม่ได้หยุดสำหรับจำนวนเต็มทั้งหมด (โดยเฉพาะจะไม่สิ้นสุดใน -1) เพื่อนของคุณถูกต้องในการระบุว่านี่คือ pseudocode และ pseudocode ไม่ได้จบลงใน stack overflow Pseudocode ไม่ได้ถูกกำหนดอย่างเป็นทางการ แต่ความคิดก็คือมันทำในสิ่งที่พูดบนดีบุก หากรหัสไม่ได้บอกว่า "ยุติด้วยข้อผิดพลาดล้นสแต็ค" ก็จะไม่มีข้อผิดพลาดสแต็คล้น

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

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

แม้ความจริงที่ว่าโปรแกรมจะหรือไม่ล้นสแต็กก็ไม่ได้ถูกกำหนดไว้อย่างดีเนื่องจากการปรับให้เหมาะสมบางอย่างเช่นการปรับให้เหมาะสมของtail callและการบันทึกช่วยจำสามารถทำให้เกิดการเรียกใช้ฟังก์ชันในพื้นที่สแต็ก ข้อกำหนดทางภาษาบางอย่างแม้ในอาณัติว่าการใช้งานจะทำการปรับแต่ง tail call เมื่อเป็นไปได้ สำหรับฟังก์ชั่นนี้f(-1)ขยายเป็นf(f(-2)); การเรียกภายนอกfคือการเรียกแบบหางดังนั้นจึงไม่ได้กดอะไรลงบนสแต็กดังนั้นจึงf(-2)เข้าสู่สแต็กเท่านั้นและจะส่งกลับ-1ดังนั้นสแต็กจะกลับไปอยู่ในสถานะเดิมที่อยู่ในช่วงเริ่มต้น ดังนั้นการเพิ่มประสิทธิภาพการโทรหางจึงf(-1)วนซ้ำตลอดไปในหน่วยความจำคงที่


3
ตัวอย่างที่โค้ดที่แปลเป็นภาษาการเขียนโปรแกรมส่งผลให้ไม่มีการล้นสแต็กคือ Haskell มันวนlet f :: Int -> Int; f n = if even n then n `div` 2 else f (f (n - 1)) in f (-1)
ซ้ำ

5

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

f(n):
   if n is even: f(n) = n/2
   else f(n) = f(f(n-1))

กับ

f(n):
   if n is even: f(n) = n/2
   else f(n) = f((n-1) / 2)

ตอนนี้การใช้งานได้รับอนุญาตให้ใช้การเรียกซ้ำหาง:

f(n):
   while n is not even do n = (n-1) / 2
   f(n) = n/2

และนี่จะวนซ้ำไปเรื่อย ๆ ถ้า n = -1 เท่านั้น


ฉันคิดว่าใน C การเรียกใช้f(-1)นั้นเป็นพฤติกรรมที่ไม่ได้กำหนดไว้ (การใช้งานอาจสันนิษฐานว่าเธรดทุกตัวจะยุติหรือทำอย่างอื่นในรายการสั้น ๆ ของกิจกรรมที่ฟังก์ชั่นนี้ไม่ได้ทำ) ดังนั้นคอมไพเลอร์สามารถทำสิ่งที่ต้องการได้ กรณี!
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.