ฟังก์ชันวนซ้ำสามารถอยู่ในบรรทัดได้หรือไม่?


137
inline int factorial(int n)
{
    if(!n) return 1;
    else return n*factorial(n-1);
}

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

คอมไพลเลอร์ตัดสินใจอย่างไรว่าจะอินไลน์ฟังก์ชันหรือไม่?

คำตอบ:


140

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

คอมไพเลอร์ที่ปรับให้เหมาะสมอาจเปลี่ยนรหัสนี้:

inline int factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

int f(int x)
{
    return factorial(x);
}

ลงในรหัสนี้:

int factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

int f(int x)
{
    if (x <= 1)
    {
        return 1;
    }
    else
    {
        int x2 = x - 1;
        if (x2 <= 1)
        {
            return x * 1;
        }
        else
        {
            int x3 = x2 - 1;
            if (x3 <= 1)
            {
                return x * x2 * 1;
            }
            else
            {
                return x * x2 * x3 * factorial(x3 - 1);
            }
        }
    }
}

ในกรณีนี้เราได้ขีดเส้นใต้ฟังก์ชัน 3 ครั้ง คอมไพเลอร์บางคนทำดำเนินการเพิ่มประสิทธิภาพนี้ ฉันจำได้ว่า MSVC ++ มีการตั้งค่าเพื่อปรับระดับของการฝังในที่จะดำเนินการกับฟังก์ชันแบบเรียกซ้ำ (ฉันเชื่อว่ามากถึง 20)


20
มันคือ #pragma inline_recursion (เปิด) เอกสารเกี่ยวกับความลึกสูงสุดไม่สอดคล้องหรือสรุปไม่ได้ ค่า 8, 16 หรือค่า #pragma inline_depth เป็นไปได้
peterchen

@peterchen หากฟังก์ชันอินไลน์กำลังเปลี่ยนค่าของอาร์กิวเมนต์หนึ่งในสิ่งที่เกิดขึ้นฉันคิดว่ามันจะดีกว่าที่จะแทรกฟังก์ชันภายในข้อเท็จจริงแทนที่จะเป็น main ขออภัยสำหรับภาษาอังกฤษของฉัน
ob_dev

1
@obounaim: คุณอาจคิดอย่างนั้น MSVC ไม่ได้
SecurityMatt

24

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

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

สำหรับกรณีที่ 2 #pragmaคุณสามารถตั้งค่าคอมไพเลอร์จำนวนมากเพื่อระบุความลึกสูงสุดที่ควรทำ ในgccคุณสามารถส่งผ่านสิ่งนี้ได้จากบรรทัดคำสั่งด้วย--max-inline-insns-recursive(ดูข้อมูลเพิ่มเติมที่นี่ )


8

AFAIK GCC จะกำจัดการเรียกหางในฟังก์ชันเรียกซ้ำถ้าเป็นไปได้ อย่างไรก็ตามฟังก์ชันของคุณจะไม่วนซ้ำ


6

คอมไพเลอร์สร้างกราฟการโทร เมื่อตรวจพบรอบการเรียกตัวเองฟังก์ชั่นจะไม่อินไลน์หลังจากความลึกที่กำหนดอีกต่อไป (n = 1, 10, 100 ไม่ว่าคอมไพเลอร์จะปรับค่าใดก็ตาม)


3

ดูคำตอบที่ให้ไว้แล้วว่าทำไมโดยทั่วไปถึงไม่ได้ผล

ในฐานะที่เป็น "เชิงอรรถ" คุณสามารถบรรลุผลที่คุณกำลังมองหา (อย่างน้อยสำหรับปัจจัยที่คุณใช้เป็นตัวอย่าง) โดยใช้แม่แบบ metaprogramming วางจาก Wikipedia:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

1
น่ารักมาก แต่โปรดสังเกตว่าการโพสต์ต้นฉบับมีอาร์กิวเมนต์ตัวแปร "int n"
โปรแกรมเมอร์ Windows

1
จริง แต่ยังมีจุดเล็ก ๆ น้อย ๆ ในการต้องการ "การซับซ้ำ" เมื่อไม่ทราบ n ในเวลาคอมไพล์ ... คอมไพเลอร์จะบรรลุสิ่งนั้นได้อย่างไร? ดังนั้นในบริบทของคำถามฉันคิดว่านี่เป็นทางเลือกที่เกี่ยวข้อง
yungchin

1
ดูตัวอย่างวิธีการทำของ Derek Park: โดยการใส่สองครั้งคุณจะได้รับ n >> 2 ครั้งและคุณจะได้รับผลตอบแทน 2 + 2 จากโค้ดผลลัพธ์
MSalters

3

ฟังก์ชันวนซ้ำบางฟังก์ชันสามารถเปลี่ยนเป็นลูปได้ ฉันเชื่อว่า gcc สามารถทำได้ แต่ฉันไม่รู้เกี่ยวกับคอมไพเลอร์อื่น ๆ


1

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

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


1

"คอมไพลเลอร์ตัดสินใจอย่างไรว่าจะอินไลน์ฟังก์ชันหรือไม่"

ขึ้นอยู่กับคอมไพลเลอร์ตัวเลือกที่ระบุหมายเลขเวอร์ชันของคอมไพเลอร์อาจมีหน่วยความจำเท่าไหร่เป็นต้น

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

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


0

คอมไพเลอร์บางตัว (เช่น Borland C ++) ไม่มีโค้ดอินไลน์ที่มีคำสั่งเงื่อนไข (if, case, while etc .. ) ดังนั้นฟังก์ชันการเรียกซ้ำในตัวอย่างของคุณจะไม่อยู่ในบรรทัด

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