语言链接

来自cppreference.com
< cpp‎ | language

提供以不同程序语言编写的模块间的链接。

extern 字符串字面量 { 声明序列 (可选) } (1)
extern 字符串字面量 声明 (2)
1) 将语言说明 字符串字面量 应用到在 声明序列 中声明的所有函数类型,具有外部链接的函数名,和具有外部链接的变量。
2) 将语言说明 字符串字面量 应用到单一声明或定义。
字符串字面量 - 所要求的语言链接的名字
声明序列 - 声明的序列,可以包含嵌套的链接说明
声明 - 一个声明

解释

所有函数类型,所有拥有外部链接的函数名,以及所有拥有外部链接的变量名,拥有一种称作语言链接的性质。语言链接封装与以另一程序语言编写的模块进行链接的要求的集合:调用约定名字重整的算法,等等。

只有以下两种语言链接保证受支持:

  1. "C++",默认的语言链接。
  2. "C",使得以 C 程序语言编写的函数进行链接,以及在 C++ 程序中定义能从 C 模块调用的函数成为可能。
extern "C"
{
    int open(const char* path_name, int flags); // C 函数声明
}
 
int main()
{
    int fd = open("test.txt", 0); // 从 C++ 程序调用 C 函数
}
 
// 此 C++ 函数能从 C 代码调用
extern "C" void handler(int)
{
    std::cout << "调用回调\n"; // 它能使用 C++
}

因为语言链接是每个函数类型的一部分,所以函数指针也要维持语言链接。函数类型的语言链接(表示调用约定)和函数名的语言链接(表示名字重整)是彼此独立的:

extern "C" void f1(void(*pf)()); // 声明一个具有 C 链接的函数 f1,
                             // 它返回 void 并接受一个指向返回 void 且不接受形参的 C 函数的指针
 
extern "C" typedef void FUNC(); // 声明 FUNC 为返回 void 且不接受形参的 C 函数类型
 
FUNC f2;            // 名字 f2 拥有 C++ 链接,但它的类型是 C 函数
extern "C" FUNC f3; // 名字 f3 拥有 C 链接且它的类型是 C 函数 void(void)
void (*pf2)(FUNC*); // 名字 pf2 拥有 C++ 链接,且它的类型是“指向返回 void 并接受
                    // 一个‘指向返回 void 且不接受形参的 C 函数的指针’的 C++ 函数的指针
 
extern "C"
{
    static void f4(); // 函数 f4 的名字拥有内部链接(无语言)
                      // 但函数的类型拥有 C 语言链接
}

在同一命名空间中具有相同名字和相同形参列表的两个函数不能拥有两个不同的语言链接(然而需要注意,形参的链接可容许这种重载,例如 std::qsortstd::bsearch 的情况)。类似地,同一命名空间中的两个变量不能拥有两种不同的语言链接。

"C" 链接的特殊规则

当类成员,尾部带有 requires 子句的友元函数 (C++20 起)或非静态成员函数在 "C" 语言块中出现时,它的链接仍然是 "C++"(但形参的类型在存在时仍然是 "C"):

extern "C"
{
    class X
    {
        void mf();           // 函数 mf 和它的类型都具有 C++ 语言链接
        void mf2(void(*)()); // 函数 mf2 具有 C++ 语言链接;
                             // 它的形参的类型是“指向 C 函数的指针”
    };
}

当一个函数或变量(在任何命名空间中)以 "C" 语言链接被声明时,所有(在任何命名空间中的)无限定名相同的函数声明和所有在全局作用域中无限定名相同的所有变量声明都必须对应相同的函数或变量。

int x;
 
namespace A
{
    extern "C" int x(); // 错误:与全局命名空间中的变量 x 同名
}
namespace A
{
    extern "C" int f();
}
 
namespace B
{
    extern "C" int f();   // A​::​f 和 B​::​f 都对应具有 C 链接的相同函数 f
}
 
int A::f() { return 98; } // 该函数的定义
namespace A
{
    extern "C" int g() { return 1; }
}
 
namespace B
{
    extern "C" int g() { return 1; } // 错误:对相同函数进行重定义
}
namespace A
{
    extern "C" int h();
}
 
extern "C" int h() { return 97; } // 具有 C 链接的函数 h 的定义
                                  // A​::​h 和 ​::​h 对应的函数相同

注意:由于有特殊规则排除了尾部带有 requires 子句的友元,因此可以使用一个虚设的子句来声明一个与全局作用域中的 "C" 函数同名的友元函数:

template<typename T>
struct A { struct B; };
 
extern "C"
{
    template<typename T>
    struct A<T>::B
    {
        friend void f(B*) requires true {} // 忽略 C 语言链接
    };
}
 
namespace Q
{
    extern "C" void f(); // 良构
}
(C++23 起)

注解

语言链接只能在命名空间作用域出现。

语言说明的花括号不建立作用域。

当语言说明发生嵌套时,只有最内层的说明会生效。

函数可以在带语言说明的声明之后,进行不带链接说明的重声明,第二个声明将重复使用首个声明的语言链接。反之则不行:如果首个声明无语言链接,那么链接会被假定为 "C++",而以其他语言链接重声明则会导致错误。

直接包含在语言链接说明之中的声明,被处理为如同它含有 extern 说明符,用以确定所声明的名字的链接以及它是否为定义

extern "C" int x; // 声明且非定义
// 上一行与 extern "C" { extern int x; } 等价
 
extern "C" { int x; } // 声明及定义
 
extern "C" double f();
static double f(); // 错误:链接冲突
 
extern "C" static void g(); // 错误:链接冲突

extern "C" 允许 C++ 程序中包含(include)含有 C 库函数的声明的头文件,但如果与 C 程序共用相同的头文件,就必须以适当的 #ifdef 隐藏 extern "C"(C 中不允许使用),通常会用 __cplusplus

#ifdef __cplusplus
extern "C" int foo(int, int); // C++ 编译器看到的
#else
int foo(int, int);            // C 编译器看到的
#endif

在现代编译器中,只有 Oracle Studio 区分带 "C" 与 "C++" 语言链接的函数类型,其他编译器不允许只在语言链接有区别的重载,包括 C++ 标准要求的重载集(std::qsortstd::bsearchstd::signalstd::atexitstd::at_quick_exit): GCC bug 2316Clang bug 6277CWG 问题 1555

extern "C"   using c_predfun   = int(const void*, const void*);
extern "C++" using cpp_predfun = int(const void*, const void*);
 
// 非良构,但大多数编译器都会接受
static_assert(std::is_same<c_predfun, cpp_predfun>::value,
              "C 和 C++ 语言链接不应区分函数类型。");
 
// 多数编译器中下列声明不声明重载
// 因为 c_predfun 与 cpp_predfun 被认为是同一类型
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);

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 4 C++98 具有内部链接的名字可以有语言链接 只有具有外部链接的名字才可以
CWG 341 C++98 有 "C" 语言链接的函数可以与某个全局变量的名字相同 此时程序非良构(在不同
翻译单元出现时不要求诊断)
CWG 564 C++98 如果两条声明只有语言链接说明不同(即 extern
后面的字符串字面量不同),那么程序非良构
改为比较这些声明实际提供的语言链接
CWG 2460 C++20 尾部带有 requires 子句且具有 "C" 语言链接的友元函数的行为有冲突 此时忽略 "C" 语言链接
CWG 2483 C++98 在 "C" 语言块中出现的静态成员函数的类型具有 "C++" 语言链接 它们具有 "C" 语言链接

引用

  • C++20 标准(ISO/IEC 14882:2020):
  • 9.11 Linkage specifications [dcl.link]
  • C++17 标准(ISO/IEC 14882:2017):
  • 10.5 Linkage specifications [dcl.link]
  • C++14 标准(ISO/IEC 14882:2014):
  • 7.5 Linkage specifications [dcl.link]
  • C++11 标准(ISO/IEC 14882:2011):
  • 7.5 Linkage specifications [dcl.link]
  • C++03 标准(ISO/IEC 14882:2003):
  • 7.5 Linkage specifications [dcl.link]