Namespaces
Variants

Language linkage

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

提供了用不同编程语言编写的程序单元之间的链接。

这也可用于将声明与其模块分离。参见 模块所有权

(since C++20)
extern 字符串字面量 { 声明序列  (可选) } (1)
extern 字符串字面量 声明 (2)
1) 将语言规范 string-literal 应用于所有函数类型、具有外部链接的函数名称以及在 declaration-seq 中声明的具有外部链接的变量。
2) 将语言规范 string-literal 应用于单个声明或定义。
string-literal - 命名所需语言链接的 未求值字符串字面量
declaration-seq - 声明序列,可包含嵌套的链接规范
declaration - 声明

目录

说明

每个函数类型、每个具有 外部链接 的函数名称,以及每个具有 外部链接 的变量名称,都具有名为 语言链接 的属性。语言链接封装了与用其他编程语言编写的程序单元进行链接所需的要求集合: 调用约定 名称修饰 (名称装饰)算法等。

只有两种语言链接保证被支持:

  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 << "Callback invoked\n"; // 可使用C++特性
}

由于语言链接是每个函数类型的一部分,指向函数的指针同样保持语言链接。函数类型的语言链接(代表调用约定)和函数名的语言链接(代表名称修饰)是相互独立的:

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

如果 同一实体的两个声明 赋予其不同的语言链接,则程序非良构;若两个声明彼此不可达,则无需诊断。不带链接说明的实体重声明会继承该实体及其类型(若存在)的语言链接。

extern "C" int f();
extern "C++" int f(); // 错误:不同的语言链接规范
extern "C" int g();
int g(); // 正确:具有C语言链接
int h(); // 默认具有C++语言链接
extern "C" int h(); // 错误:不同的语言链接规范

关于 "C" 链接的特殊规则

当类成员 、带有尾随 requires 子句 的友元函数, (C++20 起) 或非静态成员函数出现在 "C" 语言块中时,其类型的链接保持为 "C++" (但参数类型(如果存在)保持为 "C" ):

extern "C"
{
    class X
    {
        void mf();           // 函数 mf 及其类型具有 C++ 语言链接
        void mf2(void(*)()); // 函数 mf2 具有 C++ 语言链接;
                             // 参数类型为“指向 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 为声明具有 "C" 语言链接的函数或变量的声明。若另一声明 D 声明了同名实体,且满足下列任一条件,则 C D 声明同一实体:

  • D 声明一个属于全局作用域的变量。
  • 如果 C 声明变量,则 D 也声明变量。
  • 如果 C 声明函数,则 D 也声明函数。

常规重声明 不同, C D 可以具有不同的 目标作用域

extern "C"
{
    int x;
    int f();
    int g() { return 1; }
}
namespace A
{
    int x;                // 错误:重定义“x”
    int f();              // 正确:重新声明“f”
    int g() { return 1; } // 错误:重定义“g”
}

然而,此类 声明限制 仍然适用,这意味着它们应当同时声明函数或同时声明变量,且声明的实体必须具有相同类型:

namespace A
{
    extern "C" int x();
    extern "C" int y();
}
int x; // 错误:将“x”重新声明为不同类型的实体
namespace B
{
    void y(); // 错误:以不同类型重新声明“y”
}

注释

语言规范只能出现在 命名空间作用域 中。

语言规范中的花括号不会建立作用域。

当语言规范嵌套时,实际生效的是最内层的规范。

直接包含在语言链接规范中的声明,在确定所声明名称的 链接 以及它是否为 定义 时,将被视为包含 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++程序中包含声明C库函数的头文件成为可能,但如果同一个头文件需要与C程序共享,则必须使用适当的 #ifdef (通常为 __cplusplus )来隐藏 extern "C" (这在C语言中是不允许的):

#ifdef __cplusplus
extern "C" int foo(int, int); // C++ 编译器看到此声明
#else
int foo(int, int);            // C 编译器看到此声明
#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*);
// 格式错误,但被大多数编译器接受
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);

关键词

extern

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的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++23 标准 (ISO/IEC 14882:2024):
  • 9.11 链接规范 [dcl.link]
  • C++20 标准 (ISO/IEC 14882:2020):
  • 9.11 链接规范 [dcl.link]
  • C++17 标准 (ISO/IEC 14882:2017):
  • 10.5 链接规范 [dcl.link]
  • C++14 标准 (ISO/IEC 14882:2014):
  • 7.5 链接规范 [dcl.link]
  • C++11 标准 (ISO/IEC 14882:2011):
  • 7.5 链接规范 [dcl.link]
  • C++03 标准 (ISO/IEC 14882:2003):
  • 7.5 链接规范 [dcl.link]
  • C++98 标准 (ISO/IEC 14882:1998):
  • 7.5 链接规范 [dcl.link]