跳到主要内容

2 篇博文 含有标签「符号修饰」

查看所有标签

同时调用多个版本的"相同"函数

· 阅读需 2 分钟

在软件开发中,版本管理至关重要。但是,某些极端情况,需要同时运行一个接口的不同版本的函数,那么有没有可能实现呢。

比如下面有两个版本的 fun 函数,他们函数签名,都完全一样,能在一个程序中同时调用这两个函数吗?

// v1/fun.cpp
const char* fun() {
return "fun version 1";
}

// v2/fun.cpp
const char* fun() {
return "fun version 2";
}

在上述代码中,我们定义了两个版本的函数 fun(),分别位于目录 v1/v2/ 下。接下来,我们将这两个版本编译成共享库(.so 文件):

g++ -shared -fPIC -o v1/fun.so v1/fun.cpp
g++ -shared -fPIC -o v2/fun.so v2/fun.cpp

然后,我们在 main.cpp 中调用这两个共享库中的 fun 函数:

#include <dlfcn.h>
#include <iostream>

typedef const char* (*fun_ptr)();

int main() {
// 打开 v1 版本的共享库
void* handle_v1 = dlopen("v1/fun.so", RTLD_LAZY);
fun_ptr fun_v1 = reinterpret_cast<fun_ptr>(dlsym(handle_v1, "_Z3funv"));

// 打开 v2 版本的共享库
void* handle_v2 = dlopen("v2/fun.so", RTLD_LAZY);
fun_ptr fun_v2 = reinterpret_cast<fun_ptr>(dlsym(handle_v2, "_Z3funv"));

// 调用并输出两个版本的函数
std::cout << "Calling v1/fun(): " << fun_v1() << std::endl;
std::cout << "Calling v2/fun(): " << fun_v2() << std::endl;

// 关闭共享库
dlclose(handle_v1);
dlclose(handle_v2);

return 0;
}

在上述代码中,我们使用了 dlsym 函数来获取函数指针,注意参数中的符号是 _Z3funv,而不是函数名fun。在编译并运行程序之前,你可以通过 nm v1/fun.so 查看符号表以获取正确的符号名。有关符号修饰请查看 符号修饰 最后,编译并运行程序:

g++ main.cpp -ldl
./a.out

通过以上步骤,你将能够看到以下输出结果:

Calling v1/fun(): fun version 1
Calling v2/fun(): fun version 2

通过 dl 库,我们发现在一个程序中同时调用这一个函数的不同版本是可行的。(实际项目中一定要避免这种情况,做好版本管理)

上述示例代码可在 https://github.com/wenyg/dynamic_link_example 查看

C++ 符号修饰

· 阅读需 3 分钟

什么是符号修饰?

在 C++ 中,符号修饰是指编译器对函数和变量名进行的一种变换或修饰。这种修饰是为了解决 C++ 的函数重载、命名空间和类等特性引入的命名冲突问题。通过符号修饰,编译器可以在目标文件中唯一标识不同的实体,确保在链接阶段能够正确地找到并匹配相应的函数或变量。

符号修饰的原理

C++ 编译器通过一种名为“名字翻译”(Name Mangling)的技术来实现符号修饰。名字翻译将源代码中的函数和变量名转换成目标文件中的唯一标识符。这种转换过程包括以下几个方面:

1. 函数重载

C++ 允许函数重载,即在同一作用域内定义多个同名函数,但它们的参数类型或个数不同。为了在目标文件中区分这些重载函数,编译器会根据函数的参数类型和个数生成不同的符号。

2. 命名空间

命名空间是 C++ 中组织代码的一种方式,但它可能导致相同名字的函数或变量在目标文件中发生冲突。符号修饰通过在符号前添加命名空间信息,解决了这一问题。

3. 类成员函数

类的成员函数通常需要包含类的信息,以便正确访问对象的成员。符号修饰通过在符号中嵌入类的信息,确保了正确的成员函数匹配。

示例:符号修饰的实际应用

考虑以下 C++ 代码片段:

#include <iostream>

namespace Math {
class Calculator {
public:
int add(int a, int b);
};
}

int Math::Calculator::add(int a, int b) {
return a + b;
}

int main() {
Math::Calculator calc;
std::cout << calc.add(3, 4) << std::endl;
return 0;
}

通过编译并查看符号表,我们可以看到 Math::Calculator::add 函数的符号修饰:

$ nm a.out | grep add
000000000040157e T _ZN4Math9Calculator3addEii

在这个例子中,符号 _ZN4Math9Calculator3addEii 就是经过修饰的 Math::Calculator::add 函数名。

如何处理符号修饰

在正常情况下,大多数 C++ 程序员在日常编程中不需要过多关注符号修饰。编译器会自动处理符号修饰,而开发者通常只需使用函数和变量的原始名称即可。只有在涉及到库的开发、跨语言交互或者遇到符号冲突的特殊情况下,才需要更深入地了解符号修饰。