it-swarm-ko.com

C ++에서 extern "C"{#include <foo.h>}가 필요한 이유는 무엇입니까?

왜 우리는 다음을 사용해야합니까?

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

구체적으로 :

  • 언제 사용해야합니까?

  • 우리가 그것을 사용해야하는 컴파일러/링커 수준에서 무슨 일이 일어나고 있습니까?

  • 컴파일/링크와 관련하여 어떻게 사용해야합니까?

133
Landon

C와 C++는 표면적으로 비슷하지만 각각 매우 다른 코드 세트로 컴파일됩니다. C++ 컴파일러에 헤더 파일을 포함 시키면 컴파일러는 C++ 코드를 기대합니다. 그러나 C 헤더 인 경우 컴파일러는 헤더 파일에 포함 된 데이터가 특정 형식 (C++ 'ABI'또는 'Application Binary Interface')으로 컴파일 될 것으로 예상하므로 링커가 질식합니다. C++ 데이터를 C 데이터를 기대하는 함수에 전달하는 것이 좋습니다.

(실제로 딱딱한 C++의 ABI는 일반적으로 함수/메소드의 이름을 '혼자'하므로 프로토 타입을 C 함수로 플래그하지 않고 printf()을 호출하면 C++은 실제로 _Zprintf를 호출하는 코드를 생성합니다. 끝에 추가 쓰레기가 추가됩니다.)

따라서 : c 헤더를 포함 할 때 extern "C" {...}를 사용하십시오. 간단합니다. 그렇지 않으면 컴파일 된 코드가 일치하지 않아 링커가 질식합니다. 그러나 대부분의 헤더의 경우 extern가 필요하지 않습니다. 대부분의 시스템 C 헤더는 이미 C++ 코드에 포함될 수 있고 이미 extern 코드를 포함하고 있기 때문입니다.

121
duane

extern "C"는 생성 된 오브젝트 파일의 기호 이름 지정 방법을 결정합니다. extern "C"없이 함수를 선언하면 객체 파일의 심볼 이름에 C++ 이름 맹 글링이 사용됩니다. 다음은 예입니다.

주어진 test.C는 다음과 같습니다.

void foo() { }

객체 파일에서 심볼을 컴파일하고 나열하면 다음이 제공됩니다.

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

Foo 함수는 실제로 "_Z3foov"라고합니다. 이 문자열에는 무엇보다도 리턴 유형 및 매개 변수에 대한 유형 정보가 포함됩니다. 대신 test.C를 작성하면 다음과 같습니다.

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

그런 다음 기호를 컴파일하고 살펴보십시오.

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

C 연결을 얻습니다. 객체 파일에서 "foo"함수의 이름은 "foo"일 뿐이며, 이름 맹 글링에서 나오는 모든 멋진 유형 정보가 없습니다.

일반적으로 extern "C"{} 안에 헤더를 포함 시키면 코드와 함께 제공되는 코드가 C 컴파일러로 컴파일되었지만 C++에서 호출하려는 경우입니다. 이 작업을 수행하면 헤더의 모든 선언이 C 연결을 사용한다고 컴파일러에 알립니다. 코드를 링크 할 때 .o 파일에는 "_Z3fooblah"가 아닌 "foo"에 대한 참조가 포함되며, 이는 연결하려는 라이브러리의 모든 항목과 일치합니다.

대부분의 최신 라이브러리는 이러한 헤더를 보호하여 심볼이 올바른 링크로 선언되도록합니다. 예 : 많은 표준 헤더에서 찾을 수 있습니다.

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

이를 통해 C++ 코드에 헤더가 포함되면 객체 파일의 기호가 C 라이브러리의 기호와 일치해야합니다. C 헤더가 오래되었고 이러한 가드가없는 경우 C 헤더 주위에 extern "C"{}를 넣어야합니다.

108
Todd Gamblin

C++에서는 이름을 공유하는 다른 엔티티를 가질 수 있습니다. 예를 들어 다음은 foo라는 이름의 함수 목록입니다.

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

이들을 모두 구별하기 위해 C++ 컴파일러는 이름 관리 또는 데코레이션이라는 프로세스에서 각각 고유 한 이름을 만듭니다. C 컴파일러는이 작업을 수행하지 않습니다. 또한 각 C++ 컴파일러는 다른 방법으로이를 수행 할 수 있습니다.

extern "C"는 C++ 컴파일러에게 중괄호 안의 코드에서 이름을 바꾸지 말라고 지시합니다. 이를 통해 C++ 내에서 C 함수를 호출 할 수 있습니다.

21
Trent

다른 컴파일러가 이름 관리를 수행하는 방식과 관련이 있습니다. C++ 컴파일러는 C 컴파일러와 완전히 다른 방식으로 헤더 파일에서 내 보낸 심볼 이름을 엉망으로 만들므로 링크를 시도하면 심볼이 없다는 링커 오류가 발생합니다.

이 문제를 해결하기 위해 C++ 컴파일러가 "C"모드로 실행되도록 지시하므로 C 컴파일러와 같은 방식으로 이름을 변경합니다. 이렇게하면 링커 오류가 수정됩니다.

14
1800 INFORMATION

언제 사용해야합니까?

C 라이브러리를 C++ 오브젝트 파일에 링크 할 때

우리가 그것을 사용해야하는 컴파일러/링커 수준에서 무슨 일이 일어나고 있습니까?

C와 C++는 심볼 이름 지정에 다른 방식을 사용합니다. 이것은 주어진 라이브러리에서 링크 할 때 링커가 C의 스키마를 사용하도록 지시합니다.

컴파일/링크와 관련하여 어떻게 사용해야합니까?

C 이름 지정 체계를 사용하면 C 스타일 기호를 참조 할 수 있습니다. 그렇지 않으면 링커가 작동하지 않는 C++ 스타일 기호를 시도합니다.

11
Tony M

C와 C++에는 기호 이름에 대한 규칙이 다릅니다. 심볼은 링커가 컴파일러가 생성 한 하나의 객체 파일에서 "openBankAccount"함수에 대한 호출이 동일한 (또는 호환 가능한) 다른 소스 파일에서 생성 된 다른 객체 파일에서 "openBankAccount"라는 함수에 대한 참조임을 인식하는 방법입니다. 컴파일러. 이를 통해 하나 이상의 소스 파일에서 프로그램을 만들 수 있으며, 이는 큰 프로젝트에서 작업 할 때 도움이됩니다.

C에서 규칙은 매우 간단하며, 기호는 모두 단일 네임 스페이스에 있습니다. 따라서 정수 "양말"은 "양말"로 저장되고 count_socks 함수는 "count_socks"로 저장됩니다.

링커는이 간단한 심볼 이름 지정 규칙을 사용하여 C 및 C와 같은 다른 언어를 위해 작성되었습니다. 따라서 링커의 기호는 단순한 문자열입니다.

그러나 C++에서 언어를 사용하면 네임 스페이스, 다형성 및 간단한 규칙과 충돌하는 다양한 것들을 가질 수 있습니다. "add"라는 6 개의 다형성 함수 모두 다른 기호를 가져야합니다. 그렇지 않으면 다른 개체 파일에서 잘못된 기호를 사용합니다. 이것은 심볼의 이름을 "관리 (mangling)"(기술 용어)하여 수행됩니다.

C++ 코드를 C 라이브러리 또는 코드에 링크 할 때 C 라이브러리에 대한 헤더 파일과 같이 C로 작성된 모든 항목을 extern "C"해야 C++ 컴파일러에게 이러한 심볼 이름이 엉망이되지 않아야한다고 알릴 수 있습니다. 물론 C++ 코드는 엉망이되어야합니다. 그렇지 않으면 작동하지 않습니다.

10
tialaramex

C++ 컴파일러는 C 컴파일러와 다르게 심볼 이름을 만듭니다. 따라서 C 코드로 컴파일 된 C 파일에 상주하는 함수를 호출하려는 경우 C++ 컴파일러에게 해석하려는 기호 이름이 기본값과 다르게 보이도록 지시해야합니다. 그렇지 않으면 링크 단계가 실패합니다.

7
mbyrne215

C++ 파일에서 사용되는 C 컴파일러가 컴파일 한 파일에 상주하는 함수를 정의하는 헤더를 포함 할 때마다 extern "C"를 사용해야합니다. (많은 표준 C 라이브러리는 개발자에게보다 간단하게하기 위해 헤더에이 검사를 포함 할 수 있습니다)

예를 들어 util.c, util.h 및 main.cpp 파일이 3 개인 프로젝트가 있고 .c 및 .cpp 파일이 모두 C++ 컴파일러 (g ++, cc 등)로 컴파일 된 경우 실제로 필요하며 링커 오류가 발생할 수도 있습니다. 빌드 프로세스가 util.c에 일반 C 컴파일러를 사용하는 경우 util.h를 포함 할 때 extern "C"를 사용해야합니다.

C++은 함수의 매개 변수를 이름으로 인코딩합니다. 이것이 함수 오버로딩이 작동하는 방식입니다. C 함수에서 발생하는 모든 것은 이름의 시작 부분에 밑줄 ( "_")을 추가하는 것입니다. extern "C"를 사용하지 않으면 링커는 함수의 실제 이름이 _DoSomething ()이거나 DoSomething () 일 때 DoSomething @@ int @ float ()라는 함수를 찾습니다.

Extern "C"를 사용하면 C++ 컴파일러가 C++ 대신 C 명명 규칙을 따르는 함수를 찾아야한다고 C++ 컴파일러에 지시하여 위의 문제를 해결합니다.

7
HitScan

extern "C" {} construct는 중괄호 안에 선언 된 이름에 대해 조작을 수행하지 않도록 컴파일러에 지시합니다. 일반적으로 C++ 컴파일러는 함수 이름을 "향상하여"인수 및 반환 값에 대한 유형 정보를 인코딩합니다. 이것을 엉킴 이름이라고합니다. extern "C" 구문은 맹 글링을 방지합니다.

일반적으로 C++ 코드가 C 언어 라이브러리를 호출해야 할 때 사용됩니다. C++ 함수를 (예를 들어 DLL에서) C 클라이언트에 노출시킬 때 사용될 수도 있습니다.

6
Paul Lalonde

이름 맹 글링 문제를 해결하는 데 사용됩니다. extern C는 함수가 "플랫"C 스타일 API에 있음을 의미합니다.

5
Eric Z Beard

g++ 생성 된 바이너리를 디 컴파일하여 무슨 일이 일어나고 있는지 확인하십시오

C++에서 extern "C"의 영향은 무엇입니까? .

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 출력 :

g++ -c main.cpp

심볼 테이블을 디 컴파일하십시오.

readelf -s main.o

출력에는 다음이 포함됩니다.

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

통역

우리는 그것을 본다 :

  • efeg는 코드에서와 동일한 이름의 기호에 저장되었습니다.

  • 다른 상징들은 엉망이되었습니다. 그것들을 풀자 :

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

결론 : 다음 기호 유형 모두 not 엉망이되었습니다.

  • 한정된
  • 선언되었지만 정의되지 않은 (Ndx = UND), 다른 객체 파일에서 링크 또는 런타임시 제공

따라서 다음을 호출 할 때 extern "C"가 필요합니다.

  • C++에서 C : gcc에 의해 생성 된 엉킴이없는 기호를 예상하도록 g++
  • C++의 C++ : g++에게 gcc에 사용할 엉킴없는 심볼을 생성하도록 지시

extern C에서 작동하지 않는 것

이름 변경이 필요한 C++ 기능은 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++ 예제에서 실행 가능한 최소 C

완전성을 위해 그리고 거기에있는 새로운 내용을 보려면 다음도 참조하십시오 : C++ 프로젝트에서 C 소스 파일을 사용하는 방법?

C++에서 C를 호출하는 것은 매우 쉽습니다. 각 C 함수에는 가능한 하나의 엉킴이없는 기호 만 있으므로 추가 작업이 필요하지 않습니다.

main.cpp

#include <cassert>

#include "c.h"

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

c.h

#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

c.c

#include "c.h"

int f(void) { return 1; }

운영:

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

extern "C"가 없으면 링크가 다음과 같이 실패합니다.

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

g++f이 (가) 생성하지 않은 맹 글링 된 gcc을 (를) 찾을 것으로 예상하기 때문입니다.

GitHub의 예 .

C 예제에서 실행 가능한 최소 C++

C++에서 호출하는 것은 조금 더 어렵습니다. 노출하려는 각 함수의 엉킴이 아닌 버전을 수동으로 만들어야합니다.

다음은 C++ 함수 과부하를 C에 노출시키는 방법을 보여줍니다.

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);
}

운영:

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

extern "C"가 없으면 다음과 같이 실패합니다.

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

g++gcc에서 찾을 수없는 맹 글링 된 기호를 생성했기 때문입니다.

GitHub의 예 .

우분투에서 테스트 18.04.