Neden C'de yapamadığınızda neden C ++ başlık dosyasında yöntem tanımı olabilir?


23

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:


28

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.)


2
Bu kötü bir fikir değil - bu tür şeyler GNU Libc başlıklarında bile bulunabilir.
SK-logic

Peki, koşullu bir derleme yönergesinde başlık dosyasının deyimsel olarak kaydırılmasına ne dersiniz? Sonra, başlıkta VE ifadesi tanımlanmış olsa bile, yalnızca bir kez yüklenir. C için yeniyim, bu yüzden yanlış anlayabilirim.
user305964

2
@papiro Sorun, sarmanın yalnızca derleyicinin tek bir çalışması sırasında korunmasıdır. Yani foo.c bir koşuda foo.o olarak derlenmişse, diğerinde bar.c ila bar.o ve foo.o ve bar.o üçte birinde (tipik olarak olduğu gibi) a.out'a bağlanır. her nesne dosyasında bir tane olmak üzere birden çok örneğini engellemez.
David Conrad

Burada açıklanan sorun, #ifndef HEADER_Hönlenmesi gereken şey değil midir?
Robert Harvey

27

C ve C ++ bu konuda çok benzer davranır - inlinebaş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.


" işlevleri bildirstatic 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 .
Ruslan

6

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.


3

Bunu C99'da yapabilirsiniz: inlineişlevlerin başka bir yerde sağlanacağı garanti edilir, bu nedenle bir işlev satır içine alınmadıysa, tanımı bir bildirime dönüştürülür (yani uygulama atılır). Ve tabii ki kullanabilirsiniz static.


1

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 nmo MyClass::myMethodsembol 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.


-4

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.


1
Hayır, aslında gerçek bir cevap var ve C ++ 'ın temel hedeflerinden biri C ile geriye doğru uyumlu
Ed S.

4
Hayır, "Yüksek derecede C uyumluluğuna" ve "C ile gereksiz uyumsuzluğa" sahip olacak şekilde tasarlanmıştır. (her ikisi de Stroustrup'tan). Bu belirli uyumsuzluğun neden gereksiz olmadığını vurgulamak için daha ayrıntılı bir cevap verilebileceğini kabul ediyorum. Bir tane temin etmekten çekinmeyin.
Paul Butcher

Yapardım, ama Simon Richter bunu gönderdiğimde zaten vardı. "Geriye dönük uyumlu" ve "Yüksek derecede C uyumluluğu" arasındaki farkı tartışabiliriz, ancak bu cevabın yanlış olduğu gerçeği devam etmektedir. C # ve C ++ ile karşılaştırdığımızda son ifade doğruydu, ancak C ve C ++ ile pek karşılaştırmasaydık.
Ed
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.