Neden C ++ 'da "C" {#include <foo.h>} eklentisine ihtiyacımız var?


136

Neden kullanmamız gerekiyor:

extern "C" {
#include <foo.h>
}

özellikle:

  • Ne zaman kullanmalıyız?

  • Kullanmamızı gerektiren derleyici / bağlayıcı düzeyinde ne oluyor?

  • Derleme / bağlama açısından bu, onu kullanmamızı gerektiren sorunları nasıl çözüyor?

Yanıtlar:


122

C ve C ++ yüzeysel olarak benzerdir, ancak her biri çok farklı bir kod kümesine derlenir. C ++ derleyicisine sahip bir başlık dosyası eklediğinizde, derleyici C ++ kodunu bekler. Bununla birlikte, bu bir C üstbilgisiyse, derleyici başlık dosyasında bulunan verilerin belirli bir biçime (C ++ 'ABI' veya 'Uygulama İkili Arabirimi') derlenmesini bekler, böylece bağlayıcı boğulur. Bu, C ++ verilerini C verisini bekleyen bir işleve iletmek için tercih edilir.

(Gerçekten nitty-gritty'ye girmek için, C ++ 'ın ABI genellikle işlevlerinin / yöntemlerinin adlarını' yönetir ', bu nedenle printf()prototipi bir C işlevi olarak işaretlemeden çağırmak , C ++ aslında kod çağrısı _Zprintfve sonunda ekstra bok üretecektir . )

Yani: kullanın extern "C" {...} ac üstbilgisi eklerken bu kadar basit. Aksi takdirde, derlenmiş kodda bir uyumsuzluk olur ve bağlayıcı boğulur. Bununla birlikte, çoğu üstbilgi için, externçoğu sistem C üstbilgisinin zaten C ++ kodu ve zaten kodları tarafından dahil edilebileceği gerçeğini açıklayacağı için bile ihtiyacınız olmayacaktır extern.


1
Eğer daha ayrıntılı misiniz "En sistemi C başlıkları bunlar C ++ kodu ile birlikte ve zaten extern onların kod olabilir gerçeğini hesaba zaten edecektir." ?
Bulat

7
@BulatM. Bu gibi bir şey içerirler: #ifdef __cplusplus extern "C" { #endif Yani bir C ++ dosyasından dahil edildiğinde hala bir C başlığı olarak kabul edilir.
Mart'ta Calmarius

111

extern "C", oluşturulan nesne dosyasındaki sembollerin nasıl adlandırılması gerektiğini belirler. Bir işlev extern "C" olmadan bildirilirse, nesne dosyasındaki sembol adı C ++ ad yönetimi kullanır. İşte bir örnek.

Verilen test.C böyle:

void foo() { }

Nesne dosyasındaki sembolleri derlemek ve listelemek:

$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
                 U __gxx_personality_v0

Foo işlevi aslında "_Z3foov" olarak adlandırılır. Bu dize, diğer şeylerin yanı sıra, dönüş türü ve parametreleri için tür bilgisi içerir. Bunun yerine test.C yazıyorsanız:

extern "C" {
    void foo() { }
}

Sonra sembolleri derleyin ve bakın:

$ g++ -c test.C
$ nm test.o
                 U __gxx_personality_v0
0000000000000000 T foo

C bağlantısını alırsınız. Nesne dosyasındaki "foo" işlevinin adı sadece "foo" dur ve ad manglinginden gelen tüm süslü tip bilgilerine sahip değildir.

Bununla birlikte gelen kod bir C derleyicisi ile derlenmişse, ancak C ++ 'dan çağırmaya çalışıyorsanız, genellikle "C" {} extern içine bir başlık eklersiniz. Bunu yaptığınızda, derleyiciye üstbilgideki tüm bildirimlerin C bağlantısı kullanacağını söylüyorsunuz. Kodunuzu bağladığınızda, .o dosyalarınız, "_Z3fooblah" değil, "foo" referanslarını içerecektir; bu, bağlandığınız kitaplıkta bulunanlarla eşleşir.

Modern kütüphanelerin çoğu, sembollerin doğru bağlantıyla bildirilmesi için bu başlıkların etrafına korumalar koyacaktır. Örneğin, standart başlıkların çoğunda şunları bulabilirsiniz:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

Bu, C ++ kodu üstbilgiyi içerdiğinde, nesne dosyanızdaki sembollerin C kitaplığında bulunanlarla eşleşmesini sağlar. Sadece eski ve bu korumaları yoksa C başlığınızın etrafına "C" {} eki koymanız gerekir.


22

C ++ 'da, bir adı paylaşan farklı varlıklara sahip olabilirsiniz. Örneğin, foo adlı işlevlerin listesi aşağıdadır :

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

Hepsi arasında ayrım yapmak için, C ++ derleyicisi ad yönetimi veya dekorasyon adı verilen bir işlemde her biri için benzersiz adlar oluşturur. C derleyicileri bunu yapmaz. Ayrıca, her C ++ derleyicisi bunu farklı bir yoldan yapabilir.

extern "C", C ++ derleyicisine parantez içindeki kod üzerinde herhangi bir ad yönetimi yapmamasını söyler. Bu, C işlevlerini C ++ içinden çağırmanızı sağlar.


14

Farklı derleyicilerin ad yönetimi yapma şekli ile ilgilidir. Bir C ++ derleyicisi, başlık dosyasından dışa aktarılan bir sembolün adını C derleyicisinden tamamen farklı bir şekilde değiştirir, bu nedenle bağlantı oluşturmaya çalıştığınızda eksik semboller olduğunu söyleyen bir bağlantı hatası alırsınız.

Bu sorunu çözmek için, C ++ derleyicisine "C" modunda çalışmasını söyleriz, bu nedenle C derleyicisinin yaptığı gibi ad yönetimi gerçekleştirir. Bunu yaptıktan sonra linker hataları düzeltildi.


11

C ve C ++ sembol isimleri hakkında farklı kurallara sahiptir. Semboller, bağlayıcının derleyici tarafından üretilen bir nesne dosyasında "openBankAccount" işlevine yapılan çağrının, aynı (veya uyumlu) tarafından farklı bir kaynak dosyadan üretilen başka bir nesne dosyasında "openBankAccount" olarak adlandırdığınız işleve bir başvuru olduğunu nasıl bildiklerini bilir. derleyici. Bu, birden fazla kaynak dosyadan bir program oluşturmanıza olanak tanır, bu da büyük bir projede çalışırken bir rahatlamadır.

C'de kural çok basittir, sembollerin hepsi zaten tek bir isim alanındadır. Böylece "çoraplar" tamsayısı "çoraplar" ve count_socks işlevi "count_socks" olarak saklanır.

Bu basit sembol adlandırma kuralıyla C ve C gibi diğer diller için bağlayıcılar oluşturuldu. Yani bağlayıcıdaki semboller sadece basit dizelerdir.

Ancak C ++ 'da dil, ad alanlarına, polimorfizme ve böyle basit bir kuralla çelişen diğer çeşitli şeylere sahip olmanızı sağlar. "Ekle" adı verilen polimorfik fonksiyonlarınızın altısının da farklı sembolleri olması gerekir, aksi takdirde yanlış olanı diğer nesne dosyaları tarafından kullanılır. Bu, sembol adlarının "karıştırılması" (teknik bir terim) ile yapılır.

C ++ kodunu C kitaplıklarına veya koduna bağlarken, C ++ derleyicinize bu sembol adlarının karıştırılmayacağını söylemek için C kitaplıklarına ilişkin başlık dosyaları gibi C'de yazılan herhangi bir "C" ekine ihtiyacınız vardır. C ++ kodunuz elbette karıştırılmalıdır, aksi takdirde çalışmaz.


11

Ne zaman kullanmalıyız?

C libarilerini C ++ nesne dosyalarına bağlarken

Kullanmamızı gerektiren derleyici / bağlayıcı düzeyinde ne oluyor?

C ve C ++ sembol adlandırma için farklı şemalar kullanır. Bu, bağlayıcıya verilen kütüphaneye bağlanırken C'nin şemasını kullanmasını söyler.

Derleme / bağlama açısından bu, onu kullanmamızı gerektiren sorunları nasıl çözüyor?

C adlandırma düzenini kullanmak, C stili sembollere başvurmanıza olanak tanır. Aksi takdirde bağlayıcı çalışmaz C ++ tarzı sembolleri deneyin.


7

C ++ dosyasında kullanılan bir C derleyicisi tarafından derlenen dosyada bulunan işlevleri tanımlayan bir başlık eklediğinizde extern "C" kullanmalısınız. (Birçok standart C kitaplığı, geliştiriciyi kolaylaştırmak için bu denetimi başlıklarına ekleyebilir)

Örneğin, util.c, util.h ve main.cpp olan 3 projeniz varsa ve hem .c hem de .cpp dosyaları C ++ derleyicisiyle (g ++, cc, vb.) Derlenmişse, t gerçekten gerekli ve hatta bağlayıcı hatalara neden olabilir. Derleme işleminiz util.c için normal bir C derleyicisi kullanıyorsa, util.h dosyasını eklerken extern "C" kullanmanız gerekecektir.

Olan şey C ++ fonksiyonun parametrelerini isminde kodlar. Fonksiyon aşırı yüklemesi bu şekilde çalışır. Bir C işlevinde gerçekleşme eğilimi olan tek şey, adın başına bir alt çizgi ("_") eklenmesidir. Harici "C" kullanmadan, fonksiyonun gerçek adı _DoSomething () veya sadece DoSomething () olduğunda linker DoSomething @@ int @ float () adında bir fonksiyon arayacaktır.

Extern "C" kullanmak, C ++ derleyicisine C ++ yerine C adlandırma kuralını izleyen bir işlev araması gerektiğini söyleyerek yukarıdaki sorunu çözer.


7

C ++ derleyicisi C derleyicisinden farklı sembol adları oluşturur. Bu nedenle, C kodu olarak derlenen bir C dosyasında bulunan bir işleve çağrı yapmaya çalışıyorsanız, C ++ derleyicisine çözümlemeye çalıştığı sembol adlarının varsayılandan farklı göründüğünü söylemeniz gerekir; aksi takdirde bağlantı adımı başarısız olur.


6

extern "C" {}Yapı parantez içinde bildirilen isimler üzerinde bozma gerçekleştirmek için değil derleyici talimatını verir. Normalde, C ++ derleyicisi işlev adlarını "argümanlar" ve dönüş değeri hakkında tür bilgilerini kodlayacak şekilde "geliştirir"; buna karışık ad denir . extern "C"Yapı bozma önler.

Genellikle C ++ kodunun bir C dili kitaplığını çağırması gerektiğinde kullanılır. Ayrıca C ++ işlevini (örneğin bir DLL'den) C istemcilerine açıklarken de kullanılabilir.


5

Bu, ad yönetimi sorunlarını çözmek için kullanılır. extern C, fonksiyonların "düz" C-tarzı API'da olduğu anlamına gelir.


0

g++Neler olup bittiğini görmek için oluşturulan bir ikili dosyayı ayrıştırın

Neden externgerekli olduğunu anlamak için yapılacak en iyi şey, nesne dosyalarında neler olup bittiğini bir örnekle anlamaktır:

main.cpp

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

GCC 4.8 Linux ELF çıktısıyla derleyin :

g++ -c main.cpp

Sembol tablosunu koda:

readelf -s main.o

Çıktı şunları içerir:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 _Z1fv
  9: 0000000000000006     6 FUNC    GLOBAL DEFAULT    1 ef
 10: 000000000000000c    16 FUNC    GLOBAL DEFAULT    1 _Z1hv
 11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
 12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

yorumlama

Görüyoruz ki:

  • efve egkoddakiyle aynı ada sahip sembollerde saklandı

  • diğer semboller karışıktı. Onları kaldıralım:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()

Sonuç: aşağıdaki sembol türlerinin her ikisi de karışık değildi :

  • tanımlanmış
  • Ndx = UNDbaşka bir nesne dosyasından bağlantı veya çalışma zamanında sağlanacak şekilde bildirilmiş ancak tanımlanmamış ( )

Bu nedenle extern "C", arama yaparken her ikisine de ihtiyacınız olacak :

  • C ++ 'dan C: g++tarafından üretilen yönetilmeyen sembolleri beklemek söylegcc
  • C ++ C: kullanmak g++için yönetilmeyen semboller oluşturmak için söylegcc

Harici C'de çalışmayan şeyler

Ad yönetimi gerektiren herhangi bir C ++ özelliğinin içinde çalışmayacağı açıktır extern C:

extern "C" {
    // Overloading.
    // error: declaration of C function ‘void f(int)’ conflicts with
    void f();
    void f(int i);

    // Templates.
    // error: template with C linkage
    template <class C> void f(C i) { }
}

C ++ örneğinden minimum çalıştırılabilir C

Tamlık ve yeni başlayanlar için ayrıca bakınız: C kaynak dosyaları bir C ++ projesinde nasıl kullanılır?

C ++ 'dan C çağırmak oldukça kolaydır: her C fonksiyonunda sadece bir adet karışık olmayan sembol vardır, bu yüzden fazladan çalışma gerekmez.

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

ch

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

cc

#include "c.h"

int f(void) { return 1; }

Çalıştırmak:

g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out

Olmadan extern "C"bağlantı başarısız:

main.cpp:6: undefined reference to `f()'

Çünkü g++beklediği bulmak için bir mangled fhangi gccüretmedi.

GitHub Örneği .

C örneğinden minimum çalıştırılabilir C ++

C ++ 'dan çağrı yapmak biraz daha zordur: ortaya çıkarmak istediğimiz her fonksiyonun elle yönetilmeyen versiyonlarını manuel olarak yaratmalıyız.

Burada C ++ fonksiyon aşırı yüklenmelerini C'ye nasıl maruz bırakacağımızı göstereceğiz.

main.c

#include <assert.h>

#include "cpp.h"

int main(void) {
    assert(f_int(1) == 2);
    assert(f_float(1.0) == 3);
    return 0;
}

cpp.h

#ifndef CPP_H
#define CPP_H

#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif

#endif

cpp.cpp

#include "cpp.h"

int f(int i) {
    return i + 1;
}

int f(float i) {
    return i + 2;
}

int f_int(int i) {
    return f(i);
}

int f_float(float i) {
    return f(i);
}

Çalıştırmak:

gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out

Olmadan extern "C"başarısız olur:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

çünkü bulamayan g++karışık semboller üretildi gcc.

GitHub'da örnek .

Ubuntu 18.04'te test edildi.


1
Aşağı oyu açıkladığınız için teşekkürler, şimdi her şey mantıklı.
Ciro Santilli 法轮功 冠状 病: 郝海东 法轮功
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.