C'de, 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, 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ı?
Yanıtlar:
C'de, bir başlık dosyasında bir işlev tanımlarsanız, o işlev, o 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 ikisi de additup kopyalarını içerecektir.
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.
Böylece, C'deki başlık dosyasındaki işlev tanımına sahip olabilirsiniz , bu sadece kötü stil, kötü form ve çok yönlü kötü bir fikirdir.
("Beyan etmekle", yani bedensiz bir işlev prototipi sağlamak; "tanımla" demekle, işlev gövdesinin gerçek kodunu belirtmek istiyorum; bu standart C terminolojisidir.)
#ifndef HEADER_H
önlenmesi gereken şey değil midir?
C ve C ++ bu konuda çok benzer davranır - inline
başlıklarda fonksiyonlara sahip olabilirsiniz . C ++ 'da, vücudu sınıf tanımının içinde olan herhangi bir yöntem dolaylı olarak inline
. Aynısını C'de yapmak istiyorsanız, işlevleri beyan edin static inline
.
static inline
" ... ve yine de, onu kullanan her çeviri biriminde işlevin birden çok kopyası olur. static
inline
İşlevsiz C ++ 'da yalnızca bir kopyanız olur. Uygulamanın C'deki başlıkta olması için, 1) uygulamayı inline
(örn. inline void func(){do_something();}
) Olarak işaretlemeniz ve 2) aslında bu işlevin belirli bir çeviri biriminde (örneğin void func();
) olacağını söylemeniz gerekir .
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 veri / 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.
C ++ standart teklifleri
C ++ 17 N4659 standart taslağı 10.1.6 "satır içi belirteci" o yöntemler dolaylı inline diyor:
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 tanımlanabileceğini değil , tanımlanması gerektiğini görüyoruz :
6 Satır içi 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 gördüğümüz man nm
o MyClass::myMethod
sembol birden fazla nesne dosyaları görünebilir olduğu sonucunu getirir, ELF nesne dosyaları üzerinde zayıf olarak işaretlenir:
"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 olarak kullanılır. Zayıf bir tanımlanmamış sembol bağlandığında ve sembol tanımlanmadığında, sembolün değeri sisteme özel bir şekilde hatasız olarak belirlenir. Bazı sistemlerde, büyük harf varsayılan bir değerin belirtildiğini belirtir.
Büyük olasılıkla, tam yöntem uygulamasını Java'daki sınıf tanımının içine koymanız gerekir.
Kıvrımlı parantezler ve aynı anahtar kelimelerin birçoğu ile benzer görünebilirler, ancak farklı dillerdir.