C++ name mangling

 

概念

C++为支持函数重载、命名空间、类以及期待特性, 存在name mangling(命名修饰)机制

例如重载函数, 编译阶段C++编译器会通过添加参数类型、参数个数等额外信息对函数重命名, 生成唯一符号, 以区分同名函数

链接阶段, 链接器会按照符号名来解析不同目标文件和库文件中所引用符号, 以正确区分和链接函数

  • 示例, C++中同名函数经过C++name mangling后生成唯一修饰名
graph LR;
    A[/"int Sum(int x, int y)"/]
    A1[/"_Z6Sumii"/]
    B[/"double Sum(double x, double y)"/]
    B1[/"_Z6Sumdd"/]
    C(name mangling)
    A-->C-->A1
    B-->C-->B1

函数处理

函数和变量在本质上都是地址助记符, 在链接过程中称为symbol(符号)

C语言

C语言不支持重载, 故无命名修饰机制, 每个函数都有唯一未修饰名称, 链接器可直接使用名称解析符号

  • 示例, c_module.c生成目标文件
// c_module.h
#include <stdio.h>

int AddNum(int x, int y);
void PrintValue(double num);
// c_module.c
#include "c_module.h"

int AddNum(int x, int y) {
    return x + y;
}

void PrintValue(double num) {
    printf("res = %f\n", num);
}

由于C语言编译器不会对函数名增加任何处理, 因此函数名必须唯一

错误情况

  • 示例, error.c中有两个同名函数
#include <stdio.h>

int Add(int x) {
    return x + 1;
}

double Add(double x) {
    return x + 0.1;
}

编译时报错定义类型冲突

C++

  • 示例, cpp_module.cpp生成目标文件
// cpp_module.hpp
#include <iostream>

int AddNum(int x, int y);
double AddNum(double x, double y);
void PrintValue(int num);
void PrintValue(double num);
// cpp_module.cpp
#include "cpp_module.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);
}

函数名被完全修改, 原本同名函数也变为唯一名称

C++编译器通过命名修饰机制, 对于同名函数, 只要参数类型、参数个数或返回值类型不一致也可通过编译

C/C++混合

  • 示例, math_module.c、main.cpp生成可执行文件
// math_module.h
#include <stdio.h>

int Add(int x, int y);
double GetSquareArea(double length);
// math_module.c
#include "math_module.h"

int Add(int x, int y) {
    return x + y;
}

double GetSquareArea(double length) {
    return length * length;
}
// main.cpp
#include "math_module.h"
#include <iostream>

int main() {
    int res = Add(1, 2);
    double area = GetSquareArea(3.74);
    std::cout << "Add = " << res << std::endl;
    std::cout << "SquareArea = " << area << std::endl;
    return 0;
}

库链接

日常编程中更多是通过库进行调用, 当C/C++混合编程时可能会出现链接错误

未定义错误

(1) 用C语言编译器将math_module.c生成math_module.o

(2) 使用C++编译器将main.cpp生成目标文件

(3) 链接math_module.o、main.o为可执行文件, 出现符号未定义错误

(4) 查看math_module.o与main.o符号表, 发现同一个函数名出现两个不同符号

原因分析

(1) main.cpp 预处理时, 内容展开

生成main.o时, C++编译器对main.cpp中两个原本C语言函数名AddGetSquareArea使用name mangling, 生成全新函数名_Z3Addii_Z13GetSquareAread

+ #include<stdio.h>
+ int Add(int x, int y);
+ double GetSquareArea(double length);

#include<iostream>

int main() {
    int res = Add(1, 2);
    double area = GetSquareArea(3.74);
    std::cout << "Add = " << res << std::endl;
    std::cout << "SquareArea = " << area << std::endl;
    return 0;
}

(2) math_module.o由C编译器编译生成, 没有经过命令修饰, 函数名未改变

(3) 链接时main.o按照_Z3Addii符号名到各模块查找函数引用, 结果math_module.o里符号名是AddGetSquareArea, 无法匹配, 自然出现函数未定义错误

这种情况需通过extern "C"处理

extern “C”

定义

C++编译器中提供 extern "C"/ extern "C" {}机制, 表示其后续或范围内函数名屏蔽name mangling机制, 按C语言风格处理, 保持原本名称

通常用于C++代码中调用C语言动态库, 以及C余元调用C++动态库

语法

作用函数

函数名前添加extern "C", 无论使用C或C++编译器时函数均按C语言规则编译, 不进行name mangling

extern "C" 函数声明

作用代码块

extern "C" {}表示代码块内所有函数均调用extern "C"

extern "C" {
    void Func1();
    void Func2();
    ......
}

仅C++编译时使用

预处理宏__cplusplus仅在C++编译器中定义, 可通过该宏判断代码是否被C++编译器编译

  • 示例, 仅在代码被C++编译器编译时, 对函数添加extern "C"
#if __cplusplus
extern "C" {
#endif
    void Func1();
    void Func2();
#if __cplusplus
}
#endif

特点

extern "C" 只能用于函数和全局变量声明, 不能用于类成员或模板

extern "C" 修饰函数内不能出现C++所有特性

应用

C++调用C语言动态库

  • 示例, 处理上面错误

修改main.cpp, 对于所引用C语言头文件使用extern "C" {}包裹

extern "C" {
    #include "math_module.h"
}

#include <iostream>

int main() {
    int res = Add(1, 2);
    double area = GetSquareArea(3.74);
    std::cout << "Add = " << res << std::endl;
    std::cout << "SquareArea = " << area << std::endl;
    return 0;
}

main.cpp预处理时展开

extern "C" {
    #include <stdio.h>

    int Add(int x, int y);
    double GetSquareArea(double length);
}

#include <iostream>

int main() {
    int res = Add(1, 2);
    double area = GetSquareArea(3.74);
    std::cout << "Add = " << res << std::endl;
    std::cout << "SquareArea = " << area << std::endl;
    return 0;
}

extren C ""机制, main.cpp中两个C语言函数编译时不受命名修饰影响, 依然保持原名称, 和math_module.o中符号一致, 链接错误问题解决

C语言调用C++动态库

C++动态库需要在导出函数名前添加extern "C" 或用 extern "C" {}包裹, 否则C语言无法调用