手机建站系统,北京网站建设华网天下科技,女神异闻录3剧场版,网站改版必要性C 与 C 如何相互调用
C 是在 C 语言的基础上发展而来的#xff0c;意味着一个有效的 C 程序也是一个有效的 C 程序。即使这样 C 和 C 也是两种不同的编程语言#xff0c;编译上 .c 采用 gcc 编译#xff0c;.cpp 采用 g 编译#xff0c;两者编译规则不同#xff0c;所以调…C 与 C 如何相互调用
C 是在 C 语言的基础上发展而来的意味着一个有效的 C 程序也是一个有效的 C 程序。即使这样 C 和 C 也是两种不同的编程语言编译上 .c 采用 gcc 编译.cpp 采用 g 编译两者编译规则不同所以调用上需要一些处理。
1. 示例演示
演示说明 C/C 调用函数的形式先定义 .c 文件 example.c定义函数 example()执行后在终端输出 example();。
/**file example.c*/
#include example.h
#include stdio.hint32_t example()
{printf(example(););return 0;
}再定义 .h 文件声明一下 example() 函数。
/**file example.h*/
#ifndef __EXAMPLE_H__
#define __EXAMPLE_H__#include stdint.hint32_t example();#endif /*__EXAMPLE_H__*/定义 .cpp 文件 Main.cpp定义主函数 main()执行后调用 .c 的 example() 函数并在终端输出 main();。
/**file Main.cpp*/
#include example.h
#include stdio.h
#include inc.hint32_t main()
{printf(main(););example();for (;;) {}return 0;
}再定义 .h 文件声明一下 main() 函数。
/**file inc.h*/
#ifndef __INC_H__
#define __INC_H__#include stdint.hint32_t main();#endif /* __INC_H__ */编译示例程序输出错误编译信息提示 Main.cpp 调用的函数 example() 没有定义具体如下。
C:\Users\20220615\Desktop\example\buildmake
Consolidate compiler generated dependencies of target Main.exe
[ 33%] Linking CXX executable Main.exe
Memory region Used Size Region Size %age Used
CMakeFiles/Main.exe.dir/Main.cpp.obj: In function main:
C:/Users/20220615/Desktop/example/Main.cpp:13: undefined reference to example()
collect2.exe: error: ld returned 1 exit status
make[2]: *** [CMakeFiles\Main.exe.dir\build.make:112: Main.exe] Error 1
make[1]: *** [CMakeFiles\Makefile2:82: CMakeFiles/Main.exe.dir/all] Error 2
make: *** [Makefile:90: all] Error 22. 示例分析
示例程序中 example() 已定义并且 Main.cpp 也包含了 example 的 .h 文件然而却提示未定义下面用 objdump 命令查看 example.c 和 Main.cpp 编译中间文件example.o 和 Main.o 的符号表。
没强制没要求编译中间文件后缀名所以可能被定义为其他后缀比如xxx.c.obj但本质还是 xxx.o。
查看 example.c.obj 符号表。
objdump -t example.c.obj可以看到 example.c 确实定义了 example() 函数还调用了 printf() 函数。 查看 Main.cpp.obj 符号表。
objdump -t Main.cpp.obj可以看到 Main.cpp 定义了 main() 函数调用了 printf()_Z7examplev() 函数。 Main.cpp 编译后调用 example 的名称变成 _Z7examplev和实际定义不一致所以 Main.cpp 认为 example 未定义。
3. C 命名重整技术
为什么 .cpp 编译后函数名称会发生改变这里要说到 C 的命名重整技术也叫命名粉碎Name-mangling。
Name Mangling 是一种在编译过程中将函数变量名返回值通过一定算法重新改编的机制然后存储到二进制目标文件的符号表 SYMBOL TABLE。
为什么要粉碎命名
Name Mangling 作用简单来说就是编译器为了区分同名符号把同名符号重新修饰为一个全局唯一的符号。
C 允许两个函数是同名的只要它们的参数列表不同即可。
而 C 的函数重载机制就是基于此实现的重载函数的名称相同为了让链接器在工作的时候不陷入困惑将所有名字重新编码生成全局唯一名称不重复的函数让链接器能够准确识别每个名字所对应的对象。
所以重载函数就是编译器在编译阶段自动根据参数以及类型给同名函数取不同的名称来实现的C 的 namespace 原理也是相同。
粉碎规则是什么
Name-mangling 不是一个非常新的技术在 C 语言中也有我们在汇编 C 语言时经常看到的以 下划线 _ 开头的函数名其实就是 C 编译器将函数名进行了Name-mangling。 但在 C 中要复杂的多因为 C 支持 overload 和override这就要求 C 编译器必须要有 Name-mangling 的规则。
(1) 在 C 类外的变量名称完全不进行调整名为 arr 的数组调整后还是 arr。调整不与类相关的函数名称是根据参数类型用 __F 后缀和一串代表参数类型的字母组成的。
(2) 参数类型会被简写规则如下。
void:v, int:i, float:f, char:c, double:d, varages:e,
long:l, unsigned:U, const:C, volatile:V, function:f例如 函数 func(floatintunsigned char) 可能粉碎为 func__FfiUc。
(3) 类名称认为是有类型的粉碎成名称长度结合名称如 Pair 粉碎为 4Pair。
(4) 多个层次的内部类名称用 Q 来编码后面跟随数字标明层次数随后组合类名和名称长度。
例如First::Second::Third 变成了 Q35First6Second5Third。
而一个有两个类参数的函数 f(PairFirst::Second::Third) 变成f__F4PairQ35First6Second5Third。
(5) 类成员函数粉碎规则为函数名结合下划线类名称F 和参数类型简写。
例如cl::fn(void) 粉碎为 fn__2clFv所有操作符也都粉碎成 4-5 个字符的名字如 __ml 代表 * 乘法操作。
命名粉碎 C 是必须的不然就无法实现重载但粉碎规则没有统一标准各编译器之间不兼容这也是 C 兼容问题显著的原因。
4. extern “C”
C 是单一命名空间的不允许函数重载也就是说在一个编译和链接的范围之内C 不允许同名对象。所以 C 不用对名字进行处理源码是什么编译后也就是什么。
所以 C 和 C 中对同一个函数经过编译后生成的函数名是不同的这就会导致一个问题如果在 C 中调用一个使用 C 语言编写模块中的某个函数C 是按照 C 的名称修饰方式来查找并链接这个函数就会发生链接错误。
例如 C 编译 void hello()最终函数依旧为 hello而 C 编译会把函数编译成 _Z4hellov。
所以这里要说到 C 引入的 extern C 语法它用来告诉 g 在函数声明上使用 C 的调用约定处理函数名以便在链接阶段能够正确解析函数名。
链接规范的用法有两种可以单个声明链接规范这样。
extern C void example();可以一组声明链接规范这样。
extern C
{int32_t hello();void example();
}C 函数被 C 函数调用是不需要 extern C 修饰的被 C 函数调用时才需要所以 g 编译时加入 extern “C”而 gcc 编译时跳过所以可以使用条件编译控制 extern “C” 的作用g 定义了特定宏 __cplusplus。
#ifdef __cplusplus
extern C {
#endif#ifdef __cplusplus
}
#endif使用条件编译一是 C 调用 C 不需要当然二是 gcc 并不识别 C 这个关键字。
5. C 调用 C
调整前面 example 函数的头文件增加 extern “C” 声明告诉 g 应该按照 C 的方式来处理函数名。
/**file example.h*/
#ifndef __EXAMPLE_H__
#define __EXAMPLE_H__#include stdint.h#ifdef __cplusplus
extern C {
#endifint32_t example();#ifdef __cplusplus
}
#endif#endif /*__EXAMPLE_H__*/再次编译示例程序Main.cpp 不再提示调用的函数 example() 没有定义。
C:\Users\20220615\Desktop\example\buildmake
[ 33%] Building CXX object CMakeFiles/Main.exe.dir/Main.cpp.obj
[ 66%] Building C object CMakeFiles/Main.exe.dir/example.c.obj
[100%] Linking CXX executable Main.exe
Memory region Used Size Region Size %age Used
[100%] Built target Main.exe另一种方式是应用单个声明链接规范直接在调用之前临时进行声明如下。
/**file Main.cpp*/
#include stdio.h
#include inc.hextern C int32_t example();int32_t main()
{printf(main(););example();for (;;) {}return 0;
}6. C 调用 C
演示说明 C 调用 C 函数的形式先定义 .cpp 文件 example.cpp定义函数 example()执行后在终端输出 example();。
/**file example.c*/
#include example.h
#include stdio.hint32_t example()
{printf(example(););return 0;
}再定义 .h 文件声明一下 example() 函数使用 extern “C” 声明告诉 g 把 C 函数按照 C 的方式进行处理。
/**file example.h*/
#ifndef __EXAMPLE_H__
#define __EXAMPLE_H__#include stdint.h#ifdef __cplusplus
extern C {
#endifint32_t example();#ifdef __cplusplus
}
#endif#endif /*__EXAMPLE_H__*/定义 .c 文件 Main.c定义主函数 main()执行后调用 .cpp 的 example() 函数并在终端输出 main();。
/**file Main.cpp*/
#include example.h
#include stdio.h
#include inc.hint32_t main()
{printf(main(););example();for (;;) {}return 0;
}再定义 .h 文件声明一下 main() 函数。
/**file inc.h*/
#ifndef __INC_H__
#define __INC_H__#include stdint.hint32_t main();#endif /* __INC_H__ */注意对于 C 调用 C 的情况C 中没有 extern “C” 规则您需要在 C 代码中使用 extern “C” 确保 C 函数按照 C 的方式进行链接同时在 C 代码中包含相应的头文件并调用这些函数。
7. C 调用 C 成员函数
在 C 与 C 互相调用是将函数用 extern “C” 声明但只适用于非成员函数如果要调用 C 成员函数则需要提供一个简单的全局包装wrapper函数。
class Example {virtual int16_t _example(int16_t);
};int16_t BeCall(Example * ex_p, int16_t val)
{return ex_p-_example(val);
}定义 .h 文件声明把 C 函数处理成 C 函数在 C 代码中使用 extern “C” 确保 包装函数 ByCall 按照 C 的方式进行链接才能够被 C 函数调用。
extern C int16_t BeCall(Example * ex_p, int16_t val);定义 .c 文件定义函数 hello()执行后调用 .cpp 成员函数的包装函数 BeCall()。
void _for_call(Example * ex_p, int16_t val)
{int16_t d BeCall(ex_p, val);
} 8. 总结
(1) extern “C” 是什么
是 C 引入的链接规范linkage specification的概念表示法为 extern Language StringC 编译器支持的 Language String 有 “C/C” 链接规范的作用是告诉 g 对于所有使用了链接规范进行修饰的声明或定义应该按照指定语言的方式来处理。
(2) 为什么不把 include 语句放进 extern “C” 中
因为 include 文件里面还有 extern “C” 存在会造成 extern “C” 嵌套当嵌套发生以内层为准时就会出现问题。
详细查看
https://www.cnblogs.com/lizhenghn/p/3661643.html
https://www.cnblogs.com/skynet/archive/2010/09/05/1818636.html