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


136

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

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

구체적으로 특별히:

  • 언제 사용해야합니까?

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

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

답변:


122

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

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

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


1
"대부분의 시스템 C 헤더는 이미 C ++ 코드에 포함되어 있고 이미 코드를 소멸한다는 사실을 설명합니다. "에 대해 더 자세히 설명해 주시겠습니까 ? ?
Bulat M.

7
@BulatM. 여기에는 다음과 같은 내용이 #ifdef __cplusplus extern "C" { #endif 포함됩니다 . 따라서 C ++ 파일에 포함되어 있으면 여전히 C 헤더로 취급됩니다.
Calmarius

111

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"{}를 넣어야합니다.


22

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

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

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

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


14

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

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


11

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

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

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

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

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


11

언제 사용해야합니까?

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

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

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

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

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


7

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

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


6

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

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


5

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


0

g++무슨 일이 일어나고 있는지 확인하기 위해 생성 된 바이너리를 디 컴파일

extern필요한 이유를 이해하려면 가장 좋은 방법은 예제를 사용하여 오브젝트 파일에서 진행중인 작업을 자세히 이해하는 것입니다.

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

결론 : 다음 두 심볼 유형 모두 엉망 이 되지 않았습니다 .

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

따라서 extern "C"전화를 걸 때 둘 다 필요 합니다.

  • C ++의 C : tell g++ 에 의해 생산 unmangled 문자 기대gcc
  • 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);
}

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

운영:

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.


1
downvote를 설명해 주셔서 감사합니다.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.