库的符号冲突问题
程序示例
文件结构
工程最终生成动态库 libNet.so 和 test 可执行程序,test 链接动态库libNet.so
示例代码
// lib/net.h
#ifndef _NET_H_
#define _NET_H_
class Net
{
public:
Net(){};
virtual ~Net(){};
virtual int open()=0;
};
class Server : public Net
{
public:
Server();
virtual ~Server();
virtual int open();
};
#endif
// lib/net.cpp
#include "net.h"
#include <cstdio>
Server::Server():Net(){
}
Server::~Server(){
}
int Server::open(){
printf("lib: open socket\n");
}
// lib/include/lib_api.h
#ifndef _LIB_API_H_
#define _LIB_API_H_
#ifdef __cplusplus
extern "C" {
#endif
int lib_open();
#ifdef __cplusplus
}
#endif
#endif
// lib/lib_api.cpp
#include <unistd.h>
#include "lib_api.h"
#include "net.h"
int lib_open(){
#if 1
// 此方式导致程序挂死
Net* server = new Server();
if (server != NULL){
return server->open();
}
#else
// 此方式不会导致程序挂死
Server server;
server.open();
#endif
return -1;
}
// src/server.h
#ifndef _SERVER_H_
#define _SERVER_H_
class Server
{
public:
Server();
virtual ~Server();
int user_open();
};
#endif
// src/server.cpp
#include <cstdio>
#include "lib_api.h"
#include "server.h"
Server::Server(){
}
Server::~Server(){
}
int Server::user_open(){
lib_open();
printf("User: open socket\n");
return 0;
}
// src/main.cpp
#include <iostream>
#include "lib_api.h"
#include "server.h"
int main(int argc, char** argv)
{
Server* server = new Server;
if(server != NULL){
server->user_open();
printf("User Server call open\n");
}
return 0;
}
libNet.so对外提供lib_api.h头文件,其中并没有Server的类声明,使用库的一方并不知道库内声明了Server的类名,而外部程序恰好声明了同名的Server类。
运行结果
原因分析
通过堆栈查看server的虚函数表并没有open函数,所以运行到server->open()时出现了异常。
为什么server的虚函数表里没有open函数?考虑到外部程序声明了同名的Server类型,怀疑在lib_open()内的调用的new Server(),创建的实例其实是外部声明的类型,而不是动态库内声明的Server类型。
通过在各自的Server构造函数内添加打印,查看运行结果:
// lib/net.cpp
Server::Server():Net(){
printf("Lib Server construct\n");
}
// src/server.cpp
Server::Server(){
printf("User Server construct\n");
}
现在可以得出结论了:lib/net.cpp内new出的Server对象,实际是外部程序src/server.h声明的Server对象,而不是lib/net.h中声明的Server。存在符号冲突问题。
所谓符号冲突,就是库与库之间有相同的符号,使用者不知道用哪个;例如:A 库有个符号a,B 库也有个符号a,最终app调用a时,可能用的是A 库的a,也可能是B 库的a;这样的话,就会产生歧义,假如app想调用A 库的a,但可能实际调用的却是B 库的a,这样就会造成app行为异常,或是崩溃。app本质上也是一个动态库。
解决方式
库内对Server的声明和定义,用命名空间包裹
// lib/net.h
namespace LIB{
...
class Server : public Net{
...
};
}
重新编译执行,并未存在异常。通过gdb单步调试如下:
此时对比未加命名空间的代码,查看server的虚函数表,存在open函数,因此程序运行未出现异常。