extern "C"的原理

c++符号修饰和函数签名

gcc -fleading-underscore-fno-leading-underscore来开关在C语言符号前面加上下划线_

C++为了支持类,继承,虚机制,重载,命名空间等特性,发明了**符号修饰(name decoration)or符号改编(name mangling)**机制

函数签名:包含函数信息:函数名,参数类型,所在命名空间等

​ 编译器会对其(函数签名)进行修饰,对应一个修饰后的符号

test.cc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int func(int) { return 1; } //1
float func(float) { return 1.9; } //2

class C
{
public:
int func(int) { return 1; } //3
public:
class C2
{
public:
int func(int) { return 0; } //4
};
};

namespace N
{
int func(int) { return 1; } //5
class C
{
public:
int func(int) { return 0; } //6
};
}

int main()
{
C *a = new C();
a->func(1);
C::C2 *b = new C::C2();
b->func(1);
N::C *c = new N::C();
c->func(1);
return 0;
}

gcc -c test.c编译成.o看看

6

肉眼可见默认以_Z开头,参数是int则i结尾…

可以通过c++filt命令来解析被修饰的过程

c++filt _Z4funcf -> func(flaot)

extern “C”关键字

c++为了在符号管理上和c兼容,通过extern "C"关键字来声明或定义C符号

  1. 单独使用

  2. 大括号{}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // test.cc
    #include <iostream>

    extern "C" void func();
    extern "C"
    {
    int var = 1;
    void test() {}
    }

    int main()
    {
    func();
    return 0;
    }

    查看符号:

    7

    果然编译成C符号


在c++中使用c标准库函数时extern “C”的作用

例如使用void *memset(void *,int,size_t)函数,在c++中编译后会修饰符号,在链接阶段和C库的memset符号链接肯定失败

且C语言不支持extern "C"语言

兼容方法c++编译器通过__cplusplus宏判断当前编译单元是否是C++代码,包含的<string.h>内容:

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

void *memset(void*,int,size_t);

#ifdef __cplusplus
}
#endif

所以系统头文件使用这种技巧~


extern “C” 例: C++程序调用C (普通情况)

> 不是用extern "C"的话 函数声明(未定义符号类型)是会加上签名的
> 用extern "C"则是声明和定义C的符号
  1. code

    1
    2
    3
    4
    5
    6
    7
    8
    //test.cc
    void func();

    int main()
    {
    func();
    return 0;
    }

    编译:g++ -c test.cc

    1
    2
    3
    4
    5
    6
    #include <me.h>

    void func()
    {
    ps("C func");
    }

    编译: gcc -c func.c

    可以nm看编译成.o的函数符号签名

    和我们要用的c函数符号不相同

    test.o

    3

    func.o

    4

    c++.o的函数符号和c.o的函数符号不相同

  2. 在test.cc中函数声明加上extern “C”关键字

    查看符号,发现函数名没有加上签名

    5

    nm -C 的作用

  3. 链接起来

    g++ test.o func.o

    ./a.out运行成功~


其他测试出来的方法:

例: C程序调用C++函数

  1. c++函数:

    1
    2
    3
    4
    5
    6
    7
    8
    //Print.cc
    #include <iostream>

    int Print()
    {
    std::cout << "C++ func" << std::endl;
    return 0;
    }

    编译成.o g++ -c Print.cc

    nm查看c++函数的签名:

    2

    Print()的函数签名为:_Z5Printv

  2. .c文件中调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <me.h>

    extern int _Z5Printv();

    int main()
    {
    _Z5Printv();
    return 0;
    }

    编译:gcc -c main.c

  3. 链接起来:

    用到了c++的标准库,所以最后链接是通过g++来的

    g++ main.o Print.o

    ./a.out

    运行成功~

例:C++程序调用C封装的c++函数

  1. 编译:g++ -c ManualNameMangling.cc

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //ManualNamegling.cc
    #include <me.h>

    // 等待给c封装的c++函数
    void func()
    {
    ps("c++ func\n");
    }

    // 调用封装好c++函数的c函数
    extern "C" void all();

    int main()
    {
    all();
    return 0;
    }

    查看func()的函数签名

    1

    可以看到签名是_Z4funcv

  2. 通过.c封装

    1
    2
    3
    4
    5
    6
    7
    8
    //Print.c
    extern void _Z4funcv();//声明c++.o中的函数

    //封装了c++函数
    void all()
    {
    _Z4funcv();
    }

    编译:gcc -c Print.c

    在.cc中extern "C"声明c函数,然后就能正常使用了

  3. 最后链接起来:

    g++ Print.o ManualNameMangling.o

    ./a.out

    运行成功~


1

  1. ps宏是#define ps(str) printf("%s",str)

    自己修改一下就行了

  2. nm -C -C, --demangle[=STYLE] Decode low-level symbol names into user-level names

    可以看做是把符号变为函数签名,默认是显示修饰过的符号