문제

왜 우리는 다음을 사용해야 할까요?

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

구체적으로:

  • 언제 사용해야 할까요?

  • 이를 사용해야 하는 컴파일러/링커 수준에서는 무슨 일이 일어나고 있나요?

  • 컴파일/링크 측면에서 이를 사용해야 하는 문제가 어떻게 해결됩니까?

도움이 되었습니까?

해결책

C와 C++는 표면적으로 유사하지만 각각 매우 다른 코드 세트로 컴파일됩니다.C++ 컴파일러에 헤더 파일을 포함시키면 컴파일러는 C++ 코드를 예상합니다.그러나 C 헤더인 경우 컴파일러는 헤더 파일에 포함된 데이터가 C++ 'ABI' 또는 '애플리케이션 바이너리 인터페이스'와 같은 특정 형식으로 컴파일될 것으로 예상하므로 링커가 작동하지 않습니다.이는 C 데이터를 기대하는 함수에 C++ 데이터를 전달하는 것보다 더 좋습니다.

(실제로 핵심을 다루기 위해 C++의 ABI는 일반적으로 함수/메서드의 이름을 '엉터리'하므로 다음을 호출합니다. printf() 프로토타입을 C 함수로 표시하지 않고 C++는 실제로 호출하는 코드를 생성합니다. _Zprintf, 마지막에 추가 쓰레기가 추가됩니다.)

그래서:사용 extern "C" {...} c 헤더를 포함하는 경우는 매우 간단합니다.그렇지 않으면 컴파일된 코드에 불일치가 발생하고 링커가 질식하게 됩니다.그러나 대부분의 헤더에는 extern 대부분의 시스템 C 헤더는 이미 C++ 코드에 포함될 수 있다는 사실을 설명하고 있으며 이미 extern 그들의 코드.

다른 팁

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"이며, 이름 변경에서 나오는 멋진 유형 정보가 모두 포함되어 있지 않습니다.

헤더와 함께 제공되는 코드가 C 컴파일러로 컴파일되었지만 C++에서 호출하려는 경우 일반적으로 extern "C" {} 내에 헤더를 포함합니다.이렇게 하면 헤더의 모든 선언이 C 링크를 사용한다고 컴파일러에 알리게 됩니다.코드를 링크하면 .o 파일에 "_Z3fooblah"가 아닌 "foo"에 대한 참조가 포함됩니다. 이는 링크하려는 라이브러리의 내용과 일치하기를 바랍니다.

대부분의 최신 라이브러리는 기호가 올바른 연결로 선언되도록 이러한 헤더 주위에 가드를 배치합니다.예를 들어많은 표준 헤더에서 다음을 찾을 수 있습니다.

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

이렇게 하면 C++ 코드에 헤더가 포함될 때 개체 파일의 기호가 C 라이브러리의 기호와 일치하게 됩니다.C 헤더가 오래되었고 아직 이러한 가드가 없는 경우에만 extern "C" {}를 C 헤더 주위에 넣어야 합니다.

C++에서는 이름을 공유하는 다양한 엔터티를 가질 수 있습니다.예를 들어 다음은 모두 이름이 지정된 함수 목록입니다. :

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

이들 모두를 구별하기 위해 C++ 컴파일러는 이름 변경 또는 꾸미기라는 프로세스에서 각각에 대해 고유한 이름을 만듭니다.C 컴파일러는 이것을 하지 않습니다.게다가 각 C++ 컴파일러는 이를 다른 방식으로 수행할 수 있습니다.

extern "C"는 C++ 컴파일러에게 중괄호 안의 코드에 대해 이름 변경을 수행하지 않도록 지시합니다.이를 통해 C++ 내에서 C 함수를 호출할 수 있습니다.

이는 다른 컴파일러가 이름 변경을 수행하는 방식과 관련이 있습니다.C++ 컴파일러는 C 컴파일러와 완전히 다른 방식으로 헤더 파일에서 내보낸 기호의 이름을 변조하므로 링크를 시도할 때 누락된 기호가 있다는 링커 오류가 발생합니다.

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

언제 사용해야 할까요?

C 라이브러리를 C++ 개체 파일에 연결하는 경우

컴파일러/링커 레벨에서 어떤 일이 일어나고 있습니까?

C와 C++에서는 기호 이름 지정에 서로 다른 체계를 사용합니다.이는 링커에게 주어진 라이브러리에서 링크할 때 C의 구성표를 사용하도록 지시합니다.

컴파일/링크 측면에서 어떻게 사용해야하는 문제를 해결합니까?

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

C와 C++에는 기호 이름에 대한 규칙이 다릅니다.기호는 컴파일러가 생성한 한 개체 파일의 "openBankAccount" 함수에 대한 호출이 동일한(또는 호환 가능한) 다른 소스 파일에서 생성된 다른 개체 파일의 "openBankAccount" 함수에 대한 참조라는 것을 링커가 아는 방법입니다. 컴파일러.이를 통해 하나 이상의 소스 파일로 프로그램을 만들 수 있어 대규모 프로젝트 작업 시 편리합니다.

C에서는 규칙이 매우 간단합니다. 어쨌든 기호는 모두 단일 이름 공간에 있습니다.따라서 정수 "socks"는 "socks"로 저장되고 count_socks 함수는 "count_socks"로 저장됩니다.

링커는 이 간단한 기호 명명 규칙을 사용하여 C 및 C와 같은 다른 언어용으로 구축되었습니다.따라서 링커의 기호는 단순한 문자열일 뿐입니다.

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

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

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++ 컴파일러는 C 컴파일러와 다르게 기호 이름을 생성합니다.따라서 C 코드로 컴파일된 C 파일에 있는 함수를 호출하려는 경우 해결하려는 기호 이름이 기본값과 다르게 보인다는 것을 C++ 컴파일러에 알려야 합니다.그렇지 않으면 링크 단계가 실패합니다.

그만큼 extern "C" {} 구문은 중괄호 안에 선언된 이름에 대해 맹글링을 수행하지 않도록 컴파일러에 지시합니다.일반적으로 C++ 컴파일러는 인수 및 반환 값에 대한 형식 정보를 인코딩하도록 함수 이름을 "강화"합니다.이것을 이라고 한다 망가진 이름.그만큼 extern "C" 구조는 맹 글링을 방지합니다.

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

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

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

나는 이 대답을 다음에서 옮기고 있습니다: C++에서 extern "C"의 효과는 무엇입니까? 그 질문은 이 질문과 중복된 것으로 간주되었기 때문입니다.

메인.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로 컴파일 꼬마 요정 산출:

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

해석

우리는 다음을 봅니다.

  • ef 그리고 eg 코드와 동일한 이름을 가진 기호에 저장되었습니다.

  • 다른 기호는 손상되었습니다.그것들을 풀어보자:

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

결론:다음 기호 유형은 모두 ~ 아니다 망가졌다:

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

그래서 당신은 필요합니다 extern "C" 전화할 때 둘 다:

  • C++의 C:말하다 g++ 다음에 의해 생성된 훼손되지 않은 기호를 기대합니다. 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 함수에는 맹글링되지 않은 기호가 하나만 있으므로 추가 작업이 필요하지 않습니다.

메인.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

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에서 테스트되었습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top