Özyinelemeli işlev satır içi olabilir mi?


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

Ben okuyor olarak bu , doğru bir derleyici tarafından ele alınmamış ise yukarıdaki kodu "sonsuz derleme" yol açacağını bulundu.

Derleyici bir işlevi satır içine alıp almayacağına nasıl karar verir?

Yanıtlar:


137

Birincisi, inlinebir fonksiyonun özellikleri sadece bir ipucudur. Derleyici bir inlineniteleyicinin varlığını veya yokluğunu tamamen yok sayabilir (ve çoğu zaman yapar) . Bununla birlikte, bir derleyici , sonsuz döngüyü açabildiği kadar, özyinelemeli bir işlevi satır içine alabilir. Sadece fonksiyonun "kilidini" açacağı seviyeye bir sınır koymak zorundadır.

Optimize edici bir derleyici şu kodu döndürebilir:

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

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

bu koda:

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);
            }
        }
    }
}

Bu durumda, işlevi temel olarak 3 kez eğimledik. Bazı derleyiciler yapmak bu optimizasyon gerçekleştirin. MSVC ++, özyinelemeli işlevlerde (20'ye kadar inanıyorum) yapılacak satır içi düzeyini ayarlamak için bir ayara sahip hatırlıyorum.


20
#pragma inline_recursion (açık). Maksimum derinlikle ilgili belgeler tutarlı veya sonuçsuz değildir. 8, 16 veya #pragma inline_depth değerleri mümkündür.
peterchen

@peterchen Satırlanan işlev, gerçekleşen argümanlarından birinin değerini değiştiriyorsa, işlevi ana yerine gerçekte satır içi yapmak daha iyi olduğunu düşünüyorum. İngilizcem için üzgünüm
ob_dev

1
@obounaim: Bunu düşünebilirsin. MSVC bunu yapmaz.
SecurityMatt

23

Gerçekten de, derleyiciniz akıllı bir şekilde inlineişlemezse, d işlevinizin kopyalarını yinelemeli olarak eklemeyi deneyebilir ve sonsuz büyüklükte kod oluşturabilir. Bununla birlikte, modern derleyicilerin çoğu bunu tanıyacaktır. Bunlar:

  1. Fonksiyonu satır içinde değil
  2. Belirli bir derinliğe kadar satır içine alın ve eğer o zamana kadar sonlandırılmadıysa, standart fonksiyon çağırma kuralını kullanarak fonksiyonunuzun ayrı örneğini çağırın. Bu, çok sayıda yaygın vakayı yüksek performanslı bir şekilde hallederken, geniş çağrı derinliğine sahip nadir vaka için bir geri dönüş sağlar. Bu, söz konusu işlevin kodunun hem satır içi hem de ayrı sürümlerini sakladığınız anlamına gelir.

Durum 2 için, birçok derleyicide #pragmabunun yapılması gereken maksimum derinliği belirlemek için ayarlayabileceğiniz s vardır. In gcc ile, ayrıca komut satırından bu geçebilir --max-inline-insns-recursive(Daha fazla bilgi görmek buraya ).


7

AFAIK GCC, mümkünse özyinelemeli işlevler üzerinde kuyruk çağrısı ortadan kaldıracaktır. Ancak işleviniz kuyruk özyinelemeli değildir.


6

Derleyici bir çağrı grafiği oluşturur; bir döngü kendisini çağırarak tespit edildiğinde, fonksiyon artık belirli bir derinlikten sonra satır içine alınmaz (derleyici ne ayarlı olursa olsun n = 1, 10, 100).


3

Bazı özyinelemeli işlevler, onları sonsuza kadar etkili bir şekilde hizalayan döngülere dönüştürülebilir. Gcc'nin bunu yapabileceğine inanıyorum, ancak diğer derleyiciler hakkında bilmiyorum.


2

Bunun neden tipik olarak çalışmadığına dair verilen yanıtlara bakın.

"Dipnot" olarak, şablon meta programlaması kullanarak aradığınız efekti (en azından örnek olarak kullandığınız faktöriyel için) elde edebilirsiniz . Wikipedia'dan yapıştırma:

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

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

1
Bu çok sevimli, ancak orijinal gönderinin "int n" değişken argümanı olduğunu lütfen unutmayın.
Windows programcısı

1
Doğru, ama aynı zamanda n derleme zamanında bilinmediğinde "özyinelemeli satır içi" istemenin pek bir anlamı yok ... derleyici bunu nasıl başarabilirdi? Dolayısıyla soru bağlamında bunun ilgili bir alternatif olduğunu düşünüyorum.
yungchin

1
Derek Park'ın nasıl yapılacağı konusuna bakın: İki kez satır başlayarak, n >> 2 kez geri alırsınız ve elde edilen koddan 2 + 2 geri dönüşünüz olur.
MSalters

1

Derleyici, bu tür şeyleri tespit etmek ve önlemek için bir çağrı grafiği yapar. Böylece fonksiyonun satır içi değil kendisini çağırdığını görür.

Ancak esas olarak satır içi anahtar kelime ve derleyici anahtarları tarafından kontrol edilir (Örneğin, anahtar kelime olmadan bile otomatik satır içi küçük işlevlere sahip olabilirsiniz.) Çağrı ayıklama aynası korunmayacağından Hata ayıklama derlemelerinin hiçbir zaman satır içi olmaması gerektiğini belirtmek önemlidir. kodda oluşturduğunuz çağrılar.


1

"Derleyici bir işlevi satır içine alıp almayacağına nasıl karar verir?"

Bu, derleyiciye, belirtilen seçeneklere, derleyicinin sürüm numarasına, belki de ne kadar bellek bulunduğuna, vb.

Programın kaynak kodu hala satır içi işlevler için kurallara uymak zorundadır. İşlevin satır içine girilip girilmeyeceği, satır içine alınma olasılığına (bazı bilinmeyen sayıda) hazırlık yapmanız gerekir.

Vikipedi özyinelemeli makrolar genellikle yasadışı görünüyor oldukça zayıf bilgilendirilmiş. C ve C ++ özyinelemeli çağrıları önler, ancak bir çeviri birimi özyinelemeli gibi görünen makro kodu içererek yasadışı olmaz. Montajcılarda, yinelemeli makrolar genellikle yasaldır.


0

Bazı derleyiciler (Ie Borland C ++) koşullu ifadeler içeren satır içi kodu (if, case, while vb.) İçermez, bu nedenle örneğin özyinelemeli işlevi satır içine alınmaz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.