"Statik" veya "harici" olmadan "satır içi" C99'da hiç yararlı mı?


97

Bu kodu oluşturmaya çalıştığımda

inline void f() {}

int main()
{
    f();
}

komut satırını kullanarak

gcc -std=c99 -o a a.c

Bir bağlayıcı hatası alıyorum (tanımlanmamış başvuru f). Sadece kullanırsam static inlineveya extern inlineyerine kullanırsam veya inlineile -Oderlersem (yani fonksiyon aslında satır içi olur) hata kaybolur .

Bu davranış, C99 standardının 6.7.4 (6) paragrafında tanımlanmış gibi görünmektedir:

Bir çeviri birimindeki bir işlev için tüm dosya kapsamı bildirimleri inlineişlev belirticisini içermiyorsa, externbu çeviri birimindeki tanım bir satır içi tanımdır. Satır içi tanım, işlev için harici bir tanım sağlamaz ve başka bir çeviri biriminde harici bir tanımı yasaklamaz. Bir satır içi tanım, bir çevirmenin aynı çeviri biriminde işleve yönelik herhangi bir çağrıyı uygulamak için kullanabileceği bir dış tanıma bir alternatif sağlar. İşleve yönelik bir çağrının satır içi tanımı veya harici tanımı kullanıp kullanmadığı belirtilmemiştir.

Tüm bunları doğru anlarsam inline, yukarıdaki örnekteki gibi tanımlanmış bir işleve sahip bir derleme birimi, yalnızca aynı ada sahip bir dış işlev varsa tutarlı bir şekilde derler ve kendi işlevimin mi yoksa dış işlevin mi çağrıldığını asla bilemem.

Bu davranış tamamen saçma değil mi? C99 inlineolmadan staticveya externC99'da bir işlevi tanımlamak hiç yararlı oldu mu? Bir şey mi kaçırıyorum?

Cevapların özeti

Elbette bir şeyi kaçırıyordum ve bu davranış saçma değil. :)

As Nemo açıklıyor fikir koymaktır tanımı fonksiyonunun

inline void f() {}

başlık dosyasında ve yalnızca bir bildirimde

extern inline void f();

ilgili .c dosyasında. Yalnızca externbildirim, harici olarak görülebilen ikili kod üretimini tetikler. Ve inlinebir .c dosyasında gerçekten kullanımı yoktur - yalnızca başlıklarda kullanışlıdır.

As Jonathan'ın cevabı alıntı C99 komitesinin mantığı izah etmekte inlineGörüşme yerinde görünür olmasını bir fonksiyonun tanımını gerektirir derleyici optimizasyonlar ilgili. Bu sadece tanımın başlığa yerleştirilmesiyle sağlanabilir ve elbette başlıktaki bir tanım, derleyici tarafından her görüldüğünde kod göndermemelidir. Ancak derleyici bir işlevi gerçekten satır içi yapmaya zorlanmadığından, bir yerde harici bir tanım bulunmalıdır.




@Earlz: Bağlantı için teşekkürler. Sorum, standardın alıntılanan paragrafının arkasındaki mantıkla ve yine de ve inlineolmadan kullanım durumlarının olup olmadığı ile ilgili . Ne yazık ki, bu sorunların hiçbiri bu soruda ele alınmamaktadır. staticextern
Sven Marnach

@Nemo: Bu soruyu ve cevaplarımı göndermeden önce okudum. Tekrar söylüyorum, "nasıl davranıyor?" daha ziyade "bu davranışın arkasındaki fikir nedir"? Burada bir şey kaçırdığıma oldukça eminim.
Sven Marnach

1
evet, bu yüzden "sınırda" dedim. Ben şahsen bunun tamamen farklı bir soru sorduğunu düşünüyorum
Earlz

Yanıtlar:


40

Aslında bu mükemmel cevap, sorunuzu da yanıtlıyor, bence:

Harici satır içi ne yapar?

Buradaki fikir, "satır içi" nin bir başlık dosyasında ve ardından bir .c dosyasında "harici satır içi" nin kullanılabilmesidir. "extern inline", derleyiciye hangi nesne dosyasının (harici olarak görünür) üretilen kodu içermesi gerektiğini nasıl bildirdiğinizdir.

[detaylandırmak için güncelleyin]

Bir .c dosyasında "satır içi" ("statik" veya "harici" olmadan) için herhangi bir kullanım olduğunu düşünmüyorum. Ancak bir başlık dosyasında bu mantıklıdır ve bağımsız kodu gerçekten oluşturmak için bazı .c dosyasında karşılık gelen bir "harici satır içi" bildirimi gerektirir.


1
Teşekkürler, fikri anlamaya başladım!
Sven Marnach

8
Evet, bunu şimdiye kadar hiç anlamadım, bu yüzden sorduğun için teşekkürler :-). Harici olmayan "satır içi" tanımın üstbilgiye girmesi (ancak herhangi bir kod üretimiyle sonuçlanması gerekmez), "harici satır içi" bildiriminin .c dosyasına girmesi ve aslında kodun üretilecek.
Nemo

3
Bu inline void f() {}, başlığa ve extern inline void f();.c dosyasına yazdığım anlamına mı geliyor ? Yani gerçek işlev tanımı üstbilgiye giriyor ve .c dosyası bu durumda olağan sıranın tersine çevrilmesi için yalnızca bir bildirim içeriyor mu?
Sven Marnach

1
@endolith: Sorunuzu anlamadım. Veya inline olmadan tartışıyoruz . Tabii ki iyi, ama bu soru ve cevabın konusu bu değil. staticexternstatic inline
Nemo

2
@MatthieuMoy Bunun -std=c99yerine kullanmanız gerekiyor -std=gnu89.
a3f

27

Standardın (ISO / IEC 9899: 1999) kendisinden:

Ek J.2 Tanımsız Davranış

  • ...
  • Harici bağlantıya sahip bir inlineişlev, bir işlev belirticisi ile bildirilir , ancak aynı çeviri biriminde (6.7.4) tanımlanmaz.
  • ...

C99 Komitesi bir Gerekçe yazdı ve şöyle diyor:

6.7.4 Fonksiyon belirleyicileri

C99 yeni bir özelliği:inline anahtar, C uyarlanmıştır ++ a, fonksiyon belirteci sadece işlev bildirimleri kullanılabilir. Bir arama yerinde bir işlev tanımının görünmesini gerektiren program optimizasyonları için kullanışlıdır. (Standardın bu optimizasyonların yapısını belirlemeye çalışmadığını unutmayın.)

İşlevin iç bağlantısı varsa veya dış bağlantısı varsa ve çağrı, dış tanımla aynı çeviri birimindeyse görünürlük sağlanır. Bu durumlarda, inlineişlevin bir bildiriminde ya da tanımında anahtar sözcüğün mevcudiyeti, anahtar sözcük olmadan bildirilen diğer işlevlerin çağrıları yerine bu işlevin çağrılarının optimize edilmesi gerektiği yönündeki bir tercihi belirtmenin ötesinde hiçbir etkiye sahip değildir inline.

Görünürlük, çağrının işlevin tanımından farklı bir çeviri biriminde olduğu harici bağlantıya sahip bir işlev çağrısı için bir sorundur. Bu durumda, inlineanahtar kelime, çağrıyı içeren çeviri biriminin, işlevin yerel veya satır içi bir tanımını da içermesine izin verir.

Bir program, harici bir tanımı olan bir çeviri birimi, satır içi tanımı olan bir çeviri birimi ve bir işlev için tanımlanmamış ancak bildirimi olan bir çeviri birimi içerebilir. İkinci çeviri birimindeki çağrılar, her zamanki gibi harici tanımı kullanacaktır.

Bir fonksiyonun satır içi tanımı, harici tanımdan farklı bir tanım olarak kabul edilir. Bir funcsatır içi tanımın görülebildiği bir yerde harici bağlantıya sahip bir işlev çağrısı meydana gelirse, davranış, çağrının başka bir işleve, örneğin __funcdahili bağlantıyla yapılmış olmasıyla aynıdır . Uygun bir program hangi işlevin çağrıldığına bağlı olmamalıdır. Bu, Standart'taki satır içi modeldir.

Uygun bir program, satır içi tanımı kullanan uygulamaya veya harici tanımı kullanan uygulamaya güvenmemelidir. Bir işlevin adresi her zaman harici tanıma karşılık gelen adrestir, ancak bu adres işlevi çağırmak için kullanıldığında, satır içi tanım kullanılabilir. Bu nedenle, aşağıdaki örnek beklendiği gibi davranmayabilir.

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

Uygulama, çağrılardan biri için satır içi tanımı ve diğeri saddriçin harici tanımı kullanabileceğinden, eşitlik işleminin 1 (doğru) olarak değerlendirilmesi garanti edilmez. Bu, satır içi tanım içinde tanımlanan statik nesnelerin, dış tanımdaki karşılık gelen nesnelerden farklı olduğunu gösterir. Bu, kısıtlamayı const, bu türden olmayan bir nesneyi tanımlamaya bile motive etti .

Satır içi, Standart'a, mevcut bağlayıcı teknolojisi ile uygulanabilecek şekilde eklenmiştir ve C99 satır içi satırının bir alt kümesi C ++ ile uyumludur. Bu, bir satır içi işlevin tanımını içeren tam olarak bir çeviri biriminin işlev için harici tanım sağlayan birim olarak belirtilmesi gerekerek başarılmıştır. Bu belirtim, ya inlineanahtar sözcüğü içermeyen ya da her ikisini de içeren bir bildirimi içerdiğinden inlineve externbir C ++ çevirmeni tarafından da kabul edilecektir.

C99'da satır içi yapmak, C ++ spesifikasyonunu iki şekilde genişletir. İlk olarak, bir işlev inlinebir çeviri biriminde bildirilmişse inline, diğer tüm çeviri birimlerinde bildirilmesine gerek yoktur . Bu, örneğin, kitaplık içinde satır içine alınacak, ancak yalnızca başka bir yerde harici bir tanımla kullanılabilen bir kitaplık işlevine izin verir. Harici işlev için bir sarmalayıcı işlevi kullanmanın alternatifi, ek bir ad gerektirir; ve bir çevirmen aslında satır içi ikame yapmazsa performansı da olumsuz etkileyebilir.

İkinci olarak, bir satır içi işlevin tüm tanımlarının "tamamen aynı" olması gerekliliği, programın davranışının bir çağrının görünür bir satır içi tanımıyla mı yoksa bir işlevi. Bu, bir satır içi tanımın belirli bir çeviri biriminde kullanımı için özelleştirilmesine izin verir. Örneğin, bir kütüphane fonksiyonunun harici tanımı, aynı kütüphanedeki diğer fonksiyonlardan yapılan çağrılar için gerekli olmayan bazı argüman doğrulamalarını içerebilir. Bu uzantılar bazı avantajlar sunar; ve uyumluluk konusunda endişe duyan programcılar daha katı C ++ kurallarına uyabilirler.

Uygulamaların standart kitaplık işlevlerinin satır içi tanımlarını standart başlıklarda sağlamasının uygun olmadığını unutmayın, çünkü bu, başlıklarını ekledikten sonra standart kitaplık işlevlerini yeniden bildiren bazı eski kodları bozabilir. inlineAnahtar kelime fonksiyonlarının inlining önermek için taşınabilir bir yol sağlamaktır olmak içindir. Standart başlıkların taşınabilir olması gerekmediğinden, uygulamalar şu satırlar boyunca başka seçeneklere sahiptir:

#define abs(x) __builtin_abs(x)

veya standart kütüphane işlevlerini satır içi yapmak için taşınabilir olmayan diğer mekanizmalar.


Teşekkürler, bu çok ayrıntılı ve neredeyse standardın kendisi kadar okunabilir. :-) Bir şeyleri kaçırmam gerektiğini biliyordum. Bunu nereden aldığınıza dair bir referans verebilir misiniz?
Sven Marnach

Bağlantıları bulmak için Google'ı kullanabileceğiniz aklıma geldi: open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
Sven Marnach

@Sven: URL'yi aramanızdan ödünç aldım ve cevaba ekledim. Kullandığım belge daha önce kaydettiğim gerekçenin bir kopyasıydı (2005'te), ancak aynı zamanda V5.10 idi.
Jonathan Leffler

4
Tekrar teşekkürler. Cevaptan aldığım en değerli fikir, C99 standardının gerekçesini içeren bir belge olduğu (ve muhtemelen başka standartlar için de belgeler olduğu). Nemo'nun cevabı bana bir balık verirken, bu bana balık tutmayı öğretti.
Sven Marnach

0

> Bir bağlayıcı hatası alıyorum (tanımlanmamış başvuru f)

Burada çalışır: Linux x86-64, GCC 4.1.2. Derleyicinizde bir hata olabilir; Standarttan alıntılanan paragrafta verilen programı yasaklayan hiçbir şey görmüyorum. İff yerine if'in kullanımına dikkat edin .

Bir satır içi tanımı bir sağlar harici tanımına alternatif bir çevirmen, olabilir , aynı çeviri birimi işlevine çağrı uygulamak için kullanılır.

Bu nedenle, işlevin davranışını biliyorsanız ve fonu sıkı bir döngüde çağırmak istiyorsanız, işlev çağrılarını önlemek için tanımını kopyalayıp bir modüle yapıştırabilirsiniz; veya mevcut modülün amaçları doğrultusunda eşdeğer olan (ancak giriş doğrulamasını veya hayal edebileceğiniz her türlü optimizasyonu atlayan) bir tanım sağlayabilirsiniz. Bununla birlikte, derleyici yazarının bunun yerine program boyutunu optimize etme seçeneği vardır.


2
Standart, derleyicinin her zaman aynı ada sahip bir harici işlev olduğunu varsayabileceğini ve satır içi sürüm yerine onu çağırabileceğini belirtir, bu nedenle bunun bir derleyici hatası olduğunu düşünmüyorum. Gcc 4.3, 4.4 ve 4.5 ile denedim, hepsi bir linker hatası veriyor.
Sven Marnach
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.