命名修饰
命名修饰是C++编译器特有机制
因C++支持函数重载, 导致C++编译器会对函数名称进行称为名称修饰的特殊编码, 以区分不同函数签名
而在C语言中函数名称则不会进行其他处理
这种差异导致若不经过处理C与C++混合编程中可能有同个函数不同符号情况发生
函数名称
C语言
CModule.h
#include<stdio.h>
int AddNum(int x, int y);
void PrintValue(double num);
CModule.c
#include "CModule.h"
int AddNum(int x, int y) {
return x + y;
}
void PrintValue(double num) {
printf("res = %f\n", num);
}
编译为目标文件
gcc CModule.c -c -o CModule.o
查看符号表
nm CModule.o
可见, C语言编译器不会对函数名称增加任何修饰, 因此C语言中函数名必须唯一
设有RedifinationExample.c
#include<stdio.h>
int Add(int x) {
return x + 1;
}
double Add(double x) {
return x + 0.1;
}
编译时报错
C++
CPPModule.hpp
#include<iostream>
int AddNum(int x, int y);
double AddNum(double x, double y);
void PrintValue(int num);
void PrintValue(double num);
CPPModule.cpp
#include "CPPModule.hpp"
int AddNum(int x, int y) {
return x + y;
}
double AddNum(double x, double y) {
return x + y;
}
void PrintValue(int num) {
printf("int = %d\n", num);
}
void PrintValue(double num) {
printf("double = %f\n", num);
}
编译为目标文件
g++ CPPModule.c -c -o CPPModule.o
查看符号表
nm CPPModule.o
可见C++编译器对函数名符号进行称之为命名修饰的额外修改, 其将函数参数类型等作为额外符号修饰原本函数符号
这是C++支持函数重载机制的根本原因, 即对于同名函数, 只要参数类型、参数个数或返回值类型不一致也可通过编译
extern “C”
由于C/C++编译器对函数符号处理机制不一致, 在C/C++混合编程中可能出现错误
设有文件Math.h, Math.c 与 Main.cpp三个文件编译为可执行文件
Math.h
#include<stdio.h>
int Add(int x, int y);
double GetSquareArea(double length);
Math.c
#include "Math.h"
int Add(int x, int y) {
return x + y;
}
double GetSquareArea(double length) {
return length * length;
}
Main.cpp
#include "Math.h"
#include<iostream>
int main() {
int x = 1;
int y = 2;
int res = Add(x, y);
double length = 3.74;
double area = GetSquareArea(length);
std::cout << "Add = " << res << std::endl;
std::cout << "SquareArea = " << area << std::endl;
return 0;
}
g++ Math.c Main.cpp -o Main
发现可以运行, 此时查看可执行文件符号表
发现由于均使用C++编译器, 原本C文件中的函数也被执行命令修饰, 因此可正常运行
但是在日常编程中由于更多是通过库进行调用, 此时链接时可能会出现错误
目标文件链接
对上述文件, 将Math.h 与 Math.c 编译为目标文件Math.o, 在与Main.cpp编译为目标文件Main.o 链接
生成目标文件Math.o
gcc Math.c -c -o Math.o
生成目标文件Main.o
g++ Main.cpp -c -o Main.o
未定义错误
链接, 出现符号未定义错误
g++ Math.o Main.o -o Main
此时已有#include “Math.h”, 咋还会找不到符号嘞, 真是十分且九分不对劲, 莫非是g++编译器你小子把代码给我的好处都吃回扣了吧!
淡定, 分别查看Math.o与Main.o符号表
同一个函数居然出现两个符号?
链接时Main.o里按照_Z3Addii
这个名字跑到各模块里虚招, 结果Math.o 里人家本身叫Add
, 直接寻寻觅觅寻不到符号的痕迹, 怪不得报符号未找到错误
原因分析
Main.cpp 预处理时, 文件内容展开, 此时内容变为
+ #include<stdio.h>
+ int Add(int x, int y);
+ double GetSquareArea(double length);
#include<iostream>
int main() {
int x = 1;
int y = 2;
int res = Add(x, y);
double length = 3.74;
double area = GetSquareArea(length);
std::cout << "Add = " << res << std::endl;
std::cout << "SquareArea = " << area << std::endl;
return 0;
}
之后g++编译器动手, 因为是cpp文件, 直接命名修饰大法招呼, 分分钟给两个可怜函数改换名字, 害人家父子不能相认
解决
该问题核心时对于C语言模块中函数, 编译时并不希望遭受命名修饰蹂躏
于是C++各编译器中提供一个语句 extern "C"
, 其作用为表示跟在身后函数强制按C语言风格进行编译, 命名修饰机制法外开恩了属于是
修改Math.h为
#include<stdio.h>
#if __cplusplus
extern "C" {
#endif
int Add(int x, int y);
double GetSquareArea(double length);
#if __cplusplus
}
#endif
__cplusplus
是一个宏, 在C++编译器中它有具体值, 在C语言编译器中其未定义
因此上述代码含义是, 若当前使用C++编译器, 则使用extern "C"
包裹函数, 强制按C语言风格编译, 避免命名修饰机制生效
修改后Main.cpp展开时内容如下, 聪明同学想到, 此时对于这两个函数生成符号就不受命名修饰影响, 和Math.o中符号一致了
#include<stdio.h>
#if __cplusplus
extern "C" {
#endif
int Add(int x, int y);
double GetSquareArea(double length);
#if __cplusplus
}
#endif
#include<iostream>
int main() {
int x = 1;
int y = 2;
int res = Add(x, y);
double length = 3.74;
double area = GetSquareArea(length);
std::cout << "Add = " << res << std::endl;
std::cout << "SquareArea = " << area << std::endl;
return 0;
}
编译目标文件, 链接, 查看符号表
问题解决