언어 링키지

언어 링키지 (Language linkage)

언어 링키지(language linkage) 는 서로 다른 프로그래밍 언어로 작성된 프로그램 단위 사이의 연결(linkage)을 가능하게 하는 기능이에요.

이 기능은 선언을 모듈에서 분리(detach)하는 데에도 쓸 수 있어요. 모듈 소유권(Module ownership)을 봐요. (C++20부터)

extern string-literal { declaration-seq(optional) }   (1)
extern string-literal declaration                     (2)
  1. 언어 사양 string-literal 을 declaration-seq 에 선언된 모든 함수 타입, 외부 링키지를 가진 함수 이름, 외부 링키지를 가진 변수 이름에 적용해요.
  2. 언어 사양 string-literal 을 단일 선언 또는 정의에 적용해요.
  • string-literal — 필요한 언어 링키지의 이름을 지정하는 비평가 문자열 리터럴.
  • declaration-seq — 중첩 링키지 사양을 포함할 수 있는 선언들의 시퀀스.
  • declaration — 하나의 선언.

출처: cppreference

본문

설명 (Explanation)

모든 함수 타입, 외부 링키지를 가진 모든 함수 이름, 외부 링키지를 가진 모든 변수 이름에는 언어 링키지 라는 성질이 있어요. 언어 링키지는 다른 프로그래밍 언어로 작성된 프로그램 단위와 연결하는 데 필요한 요구 사항의 집합을 담아요: 호출 규약(calling convention), 이름 맹글링(name mangling / name decoration) 알고리즘 등.

보장되는 언어 링키지는 두 가지만 있어요.

  • "C++" — 기본 언어 링키지.
  • "C" — C 프로그래밍 언어로 작성된 함수와 연결할 수 있게 하고, C++ 프로그램 안에서 C 로 작성된 단위가 호출할 수 있는 함수를 정의할 수 있게 해요.
extern "C"
{
    int open(const char *path_name, int flags); // C function declaration
}

int main()
{
    int fd = open("test.txt", 0); // calls a C function from a C++ program
}

// This C++ function can be called from C code
extern "C" void handler(int)
{
    std::cout << "Callback invoked\n"; // It can use C++
}

언어 링키지는 모든 함수 타입의 일부이므로, 함수 포인터도 언어 링키지를 유지해요. 함수 타입의 언어 링키지(호출 규약을 나타냄)와 함수 이름의 언어 링키지(이름 맹글링을 나타냄)는 서로 독립적이에요.

extern "C" void f1(void(*pf)()); // declares a function f1 with C linkage,
                             // which returns void and takes a pointer to a C function
                             // which returns void and takes no parameters

extern "C" typedef void FUNC(); // declares FUNC as a C function type that returns void
                                // and takes no parameters

FUNC f2;            // the name f2 has C++ linkage, but its type is C function
extern "C" FUNC f3; // the name f3 has C linkage and its type is C function void()
void (*pf2)(FUNC*); // the name pf2 has C++ linkage, and its type is
                    // "pointer to a C++ function which returns void and takes one
                    // argument of type 'pointer to the C function which returns void
                    // and takes no parameters'"

extern "C"
{
    static void f4(); // the name of the function f4 has internal linkage (no language)
                      // but the function's type has C language linkage
}

어떤 개체의 두 선언에 서로 다른 언어 링키지가 주어지면 프로그램은 규칙 위반이에요. 어느 선언도 다른 선언에서 도달 가능하지 않으면 진단이 요구되지 않아요. 링키지 사양 없이 개체를 재선언하면 그 개체와 (있으면) 그 타입의 언어 링키지를 상속해요.

extern "C" int f();
extern "C++" int f(); // Error: different language linkages

extern "C" int g();
int g(); // OK, has C language linkage

int h(); // has C++ language linkage by default
extern "C" int h(); // Error: different language linkages

"C" 링키지의 특별 규칙 (Special rules for "C" linkage)

클래스 멤버, 뒤에 requires 절이 있는 friend 함수(C++20부터), 또는 비정적 멤버 함수가 "C" 언어 블록에 나타나면, 그 타입들의 링키지는 "C++"로 유지돼요 (단 매개변수 타입은 있으면 "C"로 유지돼요).

extern "C"
{
    class X
    {
        void mf();           // the function mf and its type have C++ language linkage
        void mf2(void(*)()); // the function mf2 has C++ language linkage;
                             // the parameter has type "pointer to C function"
    };
}

template<typename T>
struct A { struct B; };

extern "C"
{
    template<typename T>
    struct A<T>::B
    {
        friend void f(B*) requires true {} // C language linkage ignored
    };
}

namespace Q
{
    extern "C" void f(); // not ill-formed
}

C"C" 언어 링키지로 함수나 변수를 선언하는 선언이라고 해요. 다른 선언 D가 같은 이름의 개체를 선언하고 다음 조건 중 하나를 만족하면, CD는 같은 개체를 선언해요.

  • D가 전역 범위에 속하는 변수를 선언한다.
  • C가 변수를 선언하면 D도 변수를 선언한다.
  • C가 함수를 선언하면 D도 함수를 선언한다.

보통의 재선언과 달리 CD는 서로 다른 목표 범위를 가질 수 있어요.

extern "C"
{
    int x;
    int f();
    int g() { return 1; }
}

namespace A
{
    int x;                // Error: redefines "x"
    int f();              // OK, redeclares "f"
    int g() { return 1; } // Error: redefines "g"
}

그러나 그런 선언의 제약은 여전히 적용돼요. 즉 둘 다 함수를 선언하거나 둘 다 변수를 선언해야 하고, 선언된 개체는 같은 타입이어야 해요.

namespace A
{
    extern "C" int x();
    extern "C" int y();
}

int x; // Error: redeclares "x" as a different kind of entity

namespace B
{
    void y(); // Error: redeclares "y" with a different type
}

참고 (Notes)

  • 언어 사양은 네임스페이스 범위에서만 나타날 수 있어요.
  • 언어 사양의 중괄호는 범위를 만들지 않아요.
  • 언어 사양이 중첩되면 가장 안쪽 사양이 효과를 내요.
  • 언어 링키지 사양에 직접 포함된 선언은, 선언된 이름의 링키지와 그것이 정의인지 여부를 결정할 목적으로 extern 지정자를 포함한 것처럼 취급돼요.
extern "C" int x; // a declaration and not a definition
// The above line is equivalent to extern "C" { extern int x; }

extern "C" { int x; } // a declaration and definition

extern "C" double f();
static double f(); // error: linkage conflict

extern "C" static void g(); // error: linkage conflict
  • extern "C" 덕분에 C 라이브러리 함수 선언을 담은 헤더 파일을 C++ 프로그램에 포함할 수 있어요. 그런데 같은 헤더 파일을 C 프로그램과 공유하면, C 에서 허용되지 않는 extern "C"를 적절한 #ifdef(보통 __cplusplus)로 숨겨야 해요.
#ifdef __cplusplus
extern "C" int foo(int, int); // C++ compiler sees this
#else
int foo(int, int);            // C compiler sees this
#endif
  • "C""C++" 언어 링키지로 함수 타입을 구분하는 유일한 현대 컴파일러는 Oracle Studio 뿐이에요. 다른 컴파일러들은 언어 링키지만 다른 오버로드를 허용하지 않는데, 여기엔 C++ 표준이 요구하는 오버로드 집합(std::qsort, std::bsearch, std::signal, std::atexit, std::at_quick_exit)도 포함돼요 (GCC bug 2316, Clang bug 6277, CWG issue 1555).
extern "C"   using c_predfun   = int(const void*, const void*);
extern "C++" using cpp_predfun = int(const void*, const void*);

// ill-formed, but accepted by most compilers
static_assert(std::is_same<c_predfun, cpp_predfun>::value,
              "C and C++ language linkages shall not differentiate function types.");

// following declarations do not declare overloads in most compilers
// because c_predfun and cpp_predfun are considered to be the same type
void qsort(void* base, std::size_t nmemb, std::size_t size, c_predfun*   compar);
void qsort(void* base, std::size_t nmemb, std::size_t size, cpp_predfun* compar);

키워드 (Keywords)

extern

결함 보고 (Defect reports)

동작 변경 결함 보고로 주요하게 조정된 내용은 이래요. 내부 링키지를 가진 이름도 언어 링키지를 가질 수 있다는 것을 외부 링키지 이름으로 한정함(CWG 4), "C" 링키지 함수가 전역 변수와 같은 이름을 가질 수 없게 됨(CWG 341), 서로 다른 언어 링키지 사양을 가진 두 선언도 실제 주어진 언어 링키지를 비교하도록 함(CWG 564), 뒤에 requires 절이 있는 friend 함수의 "C" 링키지 충돌을 무시하도록 함(CWG 2460), "C" 언어 블록의 정적 멤버 함수 타입 링키지가 "C"가 되도록 함(CWG 2483) 등이에요.

더 알아보기 (Learn more)