C_C++-命名修饰详解

命名修饰

命名修饰是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;
}

编译目标文件, 链接, 查看符号表

问题解决

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值