0%

extern "c"解析

简述

extern “C”的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

extern “c” 含义

extern关键字

extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在链接阶段中从模块A编译生成的目标代码中找到此函数。
与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

编译链接形式

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。
例如,假设某个函数的原型为:void foo( int x, int y );

  • 该函数被C编译器编译后在符号库中的名字为_foo
  • C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。 例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。

    C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以”.”来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

常见形式

在用C++的项目源码中,经常会不可避免的会看到下面的代码:

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" {
#endif

/*...*/

#ifdef __cplusplus
}
#endif
  • #ifdef/#endif、#ifndef/#endif用于条件编译,这里的作用就是避免重复包含头文件
  • 而#ifdef _cplusplus/#endif _cplusplus,就是用于在c++源文件中调用c源文件时,正确实现C++代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

具体调用情形

C++调用C函数

使用extern ”c“在C++源文件中声明C函数,然后就可以直接从C或者C++函数中进行调用,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// C++ code
extern "C" void f(int); // one way
extern "C" { // another way
int g(double);
double h();
};
void code(int i, double d)
{
f(i);
int ii = g(d);
double dd = h();
// ...
}

函数定义类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* C code: */
void f(int i)
{
/* ... */
}
int g(double d)
{
/* ... */
}
double h()
{
/* ... */
}

注意:由于使用了C语言的规范来进行编译,因此函数不支持重载,所以必须使用正确数目的变量

C调用C++函数

与上一种情况类似,使用extern ”c“在C++源文件中声明C++函数,然后就可以直接从C或者C++函数中进行调用,示例如下:

1
2
3
4
5
6
// C++ code:
extern "C" void f(int);
void f(int i)
{
// ...
}

函数f()可以按照下面方式使用:

1
2
3
4
5
6
7
/* C code: */
void f(int);
void cc(int i)
{
f(i);
/* ... */
}

上面用法只适用于非成员函数,如果想从C语言调用成员函数,可以参考mixing-c-and-cpp

C++包含标准C语言头文件

  • 方式1:直接包含对应的C++封装好的头文件,此时无需其他操作

    1
    2
    3
    4
    5
    6
    7
    // This is C++ code
    #include <cstdio> // Nothing unusual in #include line
    int main()
    {
    std::printf("Hello world\n"); // Nothing unusual in the call either
    // ...
    }
  • 方式2:使用C++编译器编译C文件,可以使用标准C语言的头文件和其中的函数

    1
    2
    3
    4
    5
    6
    7
    /* This is C code that I'm compiling using a C++ compiler */
    #include <stdio.h> /* Nothing unusual in #include line */
    int main()
    {
    printf("Hello world\n"); /* Nothing unusual in the call either */
    // ...
    }

    C++包含非系统的C语言头文件

  • 方式1:使用extern "C"包含**#include所在行**,以此来告诉C++编译器该头文件中的函数是C语言函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // This is C++ code
    extern "C" {
    // Get declaration for f(int i, char c, float x)
    #include "my-C-code.h"
    }
    int main()
    {
    f(7, 'x', 3.14); // Note: nothing unusual in the call
    // ...
    }
  • 方式2:采用修改头文件的方式,将extern "C" {...}加到头文件中,方便C++用户#include该头文件到C++源代码中。但是由于标准C编译器无法识别extern "C"结构,因此必须使用#ifdef进行条件编译

    1
    2
    3
    4
    5
    6
    7
    8
    // Step1:Put the following lines at the very top of your C header file (note: the symbol __cplusplus is #defined if/only-if the compiler is a C++ compiler):
    #ifdef __cplusplus
    extern "C" {
    #endif
    // Step2:Put the following lines at the very bottom of your C header file:
    #ifdef __cplusplus
    }
    #endif
  • 现在可以直接可以#include对应的头文件,而不需要添加extern "C"到C++源文件

    1
    2
    3
    4
    5
    6
    7
    8
    // This is C++ code
    // Get declaration for f(int i, char c, float x)
    #include "my-C-code.h" // Note: nothing unusual in #include line
    int main()
    {
    f(7, 'x', 3.14); // Note: nothing unusual in the call
    // ...
    }

    C++调用非系统的C函数

如果想要调用一个单独的C函数,但是不想#include声明该函数的.h文件,可以在C++源文件中使用extern "C"语法,但是注意该C函数需要使用全部的函数原型,示例如下:

1
extern "C" void f(int i, char c, float x);

如果有多个C函数,可以使用{}包含几个函数原型,如下:

1
2
3
4
5
extern "C" {
void f(int i, char c, float x);
int g(char* s, const char* s2);
double sqrtOfSumOfSquares(double a, double b);
}

之后可以直接像C++函数一样调用该C函数:

1
2
3
4
5
int main()
{
f(7, 'x', 3.14); // Note: nothing unusual in the call
// ...
}

C调用C++函数

直接用extern "C"告诉编译器该函数会被一个C编译器调用

1
2
3
4
5
6
7
8
9
// This is C++ code
// Declare f(int,char,float) using extern "C":
extern "C" void f(int i, char c, float x);
// ...
// Define f(int,char,float) in some C++ module:
void f(int i, char c, float x)
{
// ...
}

注意:如果没有正确使用extern "C",往往不会出现编译错误,而是链接错误,因为C++编译器为了支持重载,会把函数名进行转换,而C编译器不会。


参考链接: