C dilinde, başlık dosyasının içinde işlev tanımı/uygulaması olamaz. Ancak, C++ 'da üstbilgi dosyası içinde tam yöntem uygulaması olabilir. Davranış neden farklı?
C'de, bir başlık dosyasında bir işlev tanımlarsanız, o işlev, bu başlık dosyasını içeren derlenen her modülde görünür ve işlev için ortak bir sembol verilir. Yani additup işlevi header.h dosyasında tanımlanmışsa ve foo.c ve bar.c dosyalarının her ikisi de header.h içeriyorsa, foo.o ve bar.o her ikisinde de additup kopyası bulunur.
Bu iki nesne dosyasını birbirine bağladığınızda, bağlayıcı, additup sembolünün bir kereden fazla tanımlandığını görür ve buna izin vermez.
İşlevin statik olduğunu bildirirseniz, hiçbir sembol dışa aktarılmaz. Foo.o ve bar.o nesne dosyaları yine de işlevin kodunun ayrı kopyalarını içerecek ve bunları kullanabilecektir, ancak bağlayıcı işlevin herhangi bir kopyasını göremeyecektir. şikayet etmeyeceğim. Tabii ki, başka hiçbir modül de işlevi göremez. Ve programınız aynı işlevin iki özdeş kopyasıyla şişirilecektir.
İşlevi yalnızca başlık dosyasında bildirir, ancak tanımlamaz ve sonra yalnızca bir modülde tanımlarsanız, bağlayıcı işlevin bir kopyasını görür ve programınızdaki her modül onu görebilir ve kullanın. Derlediğiniz program, işlevin yalnızca bir kopyasını içerir.
Yani, can başlık dosyasında fonksiyon tanımına sahip olursunuz, bu sadece kötü bir stil, kötü bir form ve çok yönlü bir kötü fikir.
("Beyan etmekle", bedensiz bir işlev prototipi sağlamak; "tanımla" demekle, işlev gövdesinin gerçek kodunu belirtmek istiyorum; bu standart C terminolojisidir.)
C ve C++ bu konuda çok aynı şekilde davranır - başlıklarda
inline fonksiyonuna sahip olabilirsiniz. C++ 'da, gövdesi sınıf tanımının içinde olan herhangi bir yöntem dolaylı olarak
inline şeklindedir. Aynı işlemi C dilinde yapmak istiyorsanız,
static inline.
Başlık dosyası kavramı biraz açıklama gerektirir:
Derleyicinin komut satırında bir dosya verirsiniz veya '#include' yaparsınız. Derleyicilerin çoğu kaynak dosya olarak c, C, cpp, c ++ vb. Uzantılı bir komut dosyasını kabul eder. Bununla birlikte, genellikle herhangi bir keyfi uzantıyı kaynak dosyaya da etkinleştirmek için bir komut satırı seçeneği içerirler.
Genellikle komut satırında verilen dosyaya 'Kaynak' ve dahil edilen dosyaya 'Üstbilgi' denir.
Önişlemci adımı aslında hepsini alır ve her şeyin derleyiciye tek bir büyük dosya gibi görünmesini sağlar. Başlıkta veya kaynakta olanlar aslında bu noktada ilgili değildir. Genellikle bu aşamanın çıktısını gösterebilen bir derleyici seçeneği vardır.
Derleyici komut satırında verilen her dosya için derleyiciye büyük bir dosya verilir. Bu, belleği kaplayacak ve/veya diğer dosyalardan referans alınacak bir sembol oluşturacak kod/verilere sahip olabilir. Şimdi bunların her biri bir 'nesne' görüntüsü üretecek. Birbirine bağlı olan ikiden fazla nesne dosyasında aynı sembol bulunursa, bağlayıcı bir 'yinelenen sembol' verebilir. Belki de nedeni budur; kodun nesne dosyasında semboller oluşturabilecek bir başlık dosyasına yerleştirilmesi önerilmez.
'Satır içi' genellikle satır içi şeklindedir .. ancak hata ayıklama sırasında satır içi olmayabilir. Peki linker neden çoklu tanımlı hatalar vermiyor? Basit ... Bunlar 'zayıf' sembollerdir ve tüm nesnelerden gelen zayıf bir sembolün tüm verileri/kodu aynı boyutta ve içerikte olduğu sürece, bağlantılı diğer nesnelerden bir kopya ve bırak kopyasını tutacaktır. İşe yarıyor.
Bunu C99'da yapabilirsiniz:
inline işlevlerinin başka bir yerde sağlanacağı garanti edilir, bu nedenle bir işlev satır içine alınmazsa tanımı bir bildirime dönüştürülür (yani, uygulama atılır). Ve elbette,
static kullanabilirsiniz.
C++ standart teklifleri
C++ 17 N4659 standart taslak 10.1.6 "Satır içi belirteç", yöntemlerin dolaylı olarak satır içi olduğunu söylüyor:
4 Sınıf tanımında tanımlanan bir işlev satır içi bir işlevdir.
ve daha sonra, satır içi yöntemlerin tüm çeviri birimlerinde sadece must tanımlanabileceğini görüyoruz:
6 Satır içi bir işlev veya değişken, tek olarak kullanıldığı her çeviri biriminde tanımlanmalı ve her durumda tam olarak aynı tanıma sahip olmalıdır (6.2).
Bu ayrıca 12.2.1 "Üye işlevleri" başlıklı notta açıkça belirtilmiştir:
1 Bir üye işlevi kendi sınıf tanımında (11.4) tanımlanabilir, bu durumda bir satıriçi üye işlevidir (10.1.6) [...]
3 [Not: Bir programda satır içi olmayan üye işlevinin en fazla bir tanımı olabilir. Bir programda birden fazla satır içi üye işlevi tanımı olabilir. Bkz. 6.2 ve 10.1.6. - son not]
GCC 8.3 uygulaması
main.cpp
struct MyClass { void myMethod() {} }; int main() { MyClass().myMethod(); }
Sembolleri derleyin ve görüntüleyin:
g++ -c main.cpp nm -C main.o
çıktı:
U _GLOBAL_OFFSET_TABLE_ 0000000000000000 W MyClass::myMethod() U __stack_chk_fail 0000000000000000 T main
o zaman
man nm _
MyClass::myMethod sembolü, ELF nesne dosyalarında zayıf olarak işaretlenmiştir, bu da birden çok nesne dosyasında görünebileceğini gösterir:
"W" "w" Sembol, zayıf nesne sembolü olarak özel olarak etiketlenmemiş zayıf bir semboldür. Zayıf tanımlı bir sembol normal tanımlı bir sembolle bağlandığında, normal tanımlı sembol hatasız kullanılır. Zayıf bir tanımsız sembol bağlandığında ve sembol tanımlanmadığında, sembolün değeri sisteme özgü bir şekilde hatasız olarak belirlenir. Bazı sistemlerde, büyük harf varsayılan bir değerin belirtildiğini gösterir.