• extern 和 extern “C“


    extern

    extern 是用来进行外部声明的。

    谨记:声明可以多次,但是定义只能有一次。

     函数的声明extern关键字是可有可无的,因为函数本身不加修饰的话就是extern的。 

    • 当我们需要使用在其它文件中定义的全局变量或全局函数时,需要先用extern进行外部声明,然后才能在当前文件中使用该全局变量或全局函数;
    1. //util.cpp
    2. #include
    3. using namespace std;
    4. int num = 20; //全局变量
    5. void fn(){ //全局函数
    6. cout << num << endl;
    7. }
    8. //index.cpp
    9. extern int num;
    10. extern void fn();
    11. void main(){
    12. fn();
    13. num = 100;
    14. fn();
    15. }

    extern "C" 

            extern "C" 是C++特有的指令(C无法使用该指令),目的在于支持C++与C混合编程。

            extern "C" 告诉C++编译器用C规则编译指定的代码(除函数重载外,extern “C”不影响C++其他特性)。

            因为C和C++的编译规则不一样,主要区别体现在编译期间生成函数符号的规则不一致。

            由于C++需要支持重载,单纯的函数名无法区分出具体的函数,所以在编译阶段就需要将形参列表作为附加项增加到函数符号中。如以下代码

    1. void Function(int a, int b)
    2. {
    3. printf("Hello!!! a = %d, b = %d\n", a, b);
    4. }

    C和C++对应的的汇编码如下

    • C汇编结果
    1. ...
    2. Function:
    3. .LFB11:
    4. .cfi_startproc
    5. movl %esi, %edx
    6. xorl %eax, %eax
    7. movl %edi, %esi
    8. movl $.LC0, %edi
    9. jmp printf
    10. .cfi_endproc
    11. ...
    • C++汇编结果
    1. ...
    2. _Z8Functionii:
    3. .LFB12:
    4. .cfi_startproc
    5. movl %esi, %edx
    6. xorl %eax, %eax
    7. movl %edi, %esi
    8. movl $.LC0, %edi
    9. jmp printf
    10. .cfi_endproc
    11. ...

    容易发现,两段代码的区别仅在于函数 Function(int a, int b) 编译后对应的符号不同

    C:Function

    C++:_Z8Functionii

    C++编出来的函数符号明显比C的多出了一些信息(如ii),这里多出来的后缀信息就是形参列表的参数类型信息。

    extern "C"例子1

    1. /* MyFunction.c */
    2. void Function(int a, int b)
    3. {
    4. printf("Hello!!! a = %d, b = %d\n", a, b);
    5. }
    6. /* main,cpp */
    7. extern void Function(int a, int b);
    8. int main()
    9. {
    10. Function(1, 2);
    11. }

    以上代码,C提供写了一个函数,用C++代码调用该函数,看起来没什么问题,但是编译的时候...

    找不到对Function(int, int)的定义

    来看看两个文件的汇编结果。

    1. .file "MyFunction.c"
    2. .section .rodata.str1.1,"aMS",@progbits,1
    3. .LC0:
    4. .string "Hello!!! a = %d, b = %d\n"
    5. .text
    6. .p2align 4,,15
    7. .globl Function
    8. .type Function, @function
    9. Function:
    10. .LFB11:
    11. .cfi_startproc
    12. movl %esi, %edx
    13. xorl %eax, %eax
    14. movl %edi, %esi
    15. movl $.LC0, %edi
    16. jmp printf
    17. .cfi_endproc
    18. .LFE11:
    19. .size Function, .-Function
    20. .ident "GCC: (GNU) 6.4.1 20170727 (Red Hat 6.4.1-1)"
    21. .section .note.GNU-stack,"",@progbits
    1. .file "main.cpp"
    2. .section .text.startup,"ax",@progbits
    3. .p2align 4,,15
    4. .globl main
    5. .type main, @function
    6. main:
    7. .LFB0:
    8. .cfi_startproc
    9. subq $8, %rsp
    10. .cfi_def_cfa_offset 16
    11. movl $2, %esi
    12. movl $1, %edi
    13. call _Z8Functionii
    14. xorl %eax, %eax
    15. addq $8, %rsp
    16. .cfi_def_cfa_offset 8
    17. ret
    18. .cfi_endproc
    19. .LFE0:
    20. .size main, .-main
    21. .ident "GCC: (GNU) 6.4.1 20170727 (Red Hat 6.4.1-1)"
    22. .section .note.GNU-stack,"",@progbits

    可以看到,MyFunction.s(源文件为.c文件)中定义的是Function,而main.s(源文件为.cpp文件)中调用的是_Z8Functionii,函数名不一样,所以连接的时候找不到函数实现。到这里我们知道C和C++编译期间后得到的函数符号不同,所以C++代码和C代码不能互相调用。

    要想实现C、C++混合编程该怎么办呢?用extern "C"!

    所以,extern “C”的作用就是告诉C++编译器,将指定的函数用C规则编译(注意,除了函数重载外,extern “C”不影响C++的其他特性),然后后面的事情就顺理成章了。

    extern "C"例子2

    在一个dll动态链接库中,头文件如下:声明 fnDll1();

    1. #ifdef DLL1_EXPORTS
    2. #define DLL1_API __declspec(dllexport)
    3. #else
    4. #define DLL1_API __declspec(dllimport)
    5. #endif
    6. DLL1_API int fnDll1(); // extern DLL1_API int fnDll1() 也可以

    对应cpp中定义 fnDll1() 函数

    1. int fnDll1() {
    2. return 100;
    3. }

    在控制台项目中,main.cpp 通过 LoadLibrary 调用.dll

    1. void dllCall() {
    2. typedef int(*FnPoint)();
    3. HMODULE HD = LoadLibrary(L"Dll1.dll");
    4. if (!HD) {
    5. cout << "HD is null." << endl;
    6. return;
    7. }
    8. FnPoint FD = (FnPoint)GetProcAddress(HD, "fnDll1");
    9. if (!FD) {
    10. cout << "FD is null." << endl;
    11. return;
    12. }
    13. cout << FD() << endl;
    14. }

    此时运行程序,结果如下

    dll文件引入成功了,但是fnDll1函数却没有拿到。造成这种结果是因为,dll中的fnDll1通过C++进行编译,导致最终的名字并不是原来的了。

    解决方法:

    方法1:使用extern "C",修改dll头文件。这样fnDll1 仍然是 fnDll1

    1. #ifdef __cplusplus // 如果是C++就渲染`extern "C"{`;
    2. extern "C" {
    3. #endif // __cplusplus
    4. DLL1_API int fnDll1();
    5. #ifdef __cplusplus
    6. }
    7. #endif // __cplusplus
    8. extern "C" DLL1_API int fnDll1();

    方法2:使用.def导出,创建Source.def,并在配置中设置。最终名称就是.def文件中的名称。

    1. LIBRARY "Dll1"
    2. EXPORTS
    3. fnDll1 @1

     方法3(不推荐,仅供参考):使用fnDll1被C++编译之后的名字

    在命令行中cd进入.dll文件的目录下。执行:

    dumpbin -exports Dll1.dll

    将 main.cpp 修改:

    FnPoint FD = (FnPoint)GetProcAddress(HD, "?fnDll1@@YAHXZ");

    extern “C“的作用及理解_米碎师兄的博客-CSDN博客_externc

  • 相关阅读:
    后缀自动机(其二)
    Camunda BPM架构
    马尔可夫链
    GEM5 Garnet DVFS / NoC DVFS教程:ruby.clk_domain ruby.voltage_domain
    门禁闸机翼闸应用在什么领域,有什么作用
    社区团购商品数据抓取
    Flume从入门到精通一站式学习笔记
    React之使用脚手架启动页面
    fastapi 在中间件中获取requestBody
    研发管理的挑战
  • 原文地址:https://blog.csdn.net/qq_36157085/article/details/127668409