背景:我们打算在C++的工程项目中使用自定义的C语言头文件,直接#include是不行的,那怎么做?
我在VSCode(g++环境)中实验,但是遇到错误:
链接错误(未定义的引用),编译器找不到Add的定义,显然被g++给修饰了函数名,最终导致了link error。但这并不意味着我们的代码错误,因为在VS的MSVC编译器下成功了。
在MSVC(VS2019)中:
Main.cpp
Add_c.c
Add_c.h
注:Main.cpp中我把条件编译删掉了,但实质代码一样,只有编译器不一样。
结果是正确的:
下面我们考虑两个相关问题,加深理解:
为什么在C++工程中,使用自定义的C的头文件时要这样做?
C++中有一个C中没有的特性------函数重载:具有相同函数名,但是不同参数列表的两个函数在C中会报错,在C++中则不会,我们称这两个函数重载了。(顺便提一句:无法重载仅按返回值区分的函数)。
为了支持函数重载,C++引入了函数名修饰,具体修饰规则因编译器不同而不同。
我们的extern "C" { //... } 这些代码,就是让 C++ 编译器(比如g++)按照 C 的规则来处理 add.h 中的声明,不要修饰我们C中的函数名,以便我们链接时链接器可以使用C语言中的原生函数名准确找到Add的定义。避免链接错误。具体是怎样的呢?
每个源文件会被编译汇编为对应的目标文件(VS下的.obj或者GCC下的.O文件),目标文件生成后,此时链接器出场,准备把库文件和多个目标文件链接为一个可执行文件,链接器会去找Add这个函数调用的地址,在哪里找?符号表(顺便提一嘴:在逆向工程中,许多工具会通过符号表来检查全局变量和已知函数的地址。)!
下面展示一个符号表:
如上图,符号表里记录了修饰后的函数名,地址......
第一列为地址,第三列为名称。
如果Add函数没有被修饰,那么Add的地址很容易被找到,如果Add被g++修饰了函数名,比如修饰成了Add_ii,那么符号表里就找不到Add的函数地址,于是报链接错误。
#include<stdio.h>时,为什么不需要这样做?
<stdio.h>是标准头文件。当你在 C++ 程序中包含这些头文件时,C++ 编译器会自动按照 C 语言的方式来处理它们,即使用 C 语言的命名规则(不进行名称修饰)。因此,不需要显式地使用 extern "C" 来处理这些头文件。也就是说:标准的 C 语言库(如 stdio.h)本身不需要这样做,因为它们已经被 C++ 编译器处理过了。
2828

被折叠的 条评论
为什么被折叠?



