为何主可执行文件与dlopen加载的共享库共用命名空间静态变量副本?
哈哈,这可是个把C++链接规则和共享库符号可见性混在一起的经典坑!我给你掰扯清楚:
先纠正一个核心误解
你说的「命名空间作用域的静态变量每个编译单元各有一份副本」本身是对的,但这个规则有个前提:变量确实是「内部链接」的静态变量,且每个编译单元都独立定义了它。而你遇到的共用问题,本质是你的变量并没有真正成为「每个编译单元独有的内部链接变量」。
最可能的原因:你误用了外部链接的全局变量
大概率是你的代码写法出了问题,比如:
- 在
badlad.h里用extern声明了命名空间变量:// badlad.h namespace BadLad { extern int sCount; extern SomeClass sBadLad; } - 然后在
badlad.cpp里定义这些变量时没加static:// badlad.cpp namespace BadLad { int sCount = 0; // 这里没有static!属于外部链接的全局变量 SomeClass sBadLad; }
这种情况下,sCount和sBadLad是外部链接的符号,会被共享库libBadLad.so导出。当主程序链接libBadLad.so后,符号已经加载到进程空间;之后用dlopen加载libPlugin.so时,因为libPlugin.so也链接了libBadLad.so,系统会直接重用进程中已有的libBadLad.so符号副本,自然就出现了主程序和libPlugin.so共用同一份变量的情况。
那真正的「每个编译单元独立副本」该怎么写?
如果你确实需要每个包含badlad.h的.cpp都有自己的sCount和sBadLad,有两种可靠写法:
写法1:头文件中直接用static定义变量
// badlad.h namespace BadLad { static int sCount = 0; static SomeClass sBadLad; }
每个包含这个头文件的.cpp编译单元,都会生成自己独立的变量副本——因为static修饰的命名空间变量是内部链接,符号只在当前编译单元可见,不会被导出到共享库的全局符号表。
写法2:用匿名命名空间(更推荐)
匿名命名空间里的变量默认是内部链接,效果和static一致,但代码可读性更好:
// badlad.h namespace BadLad { namespace { int sCount = 0; SomeClass sBadLad; } }
额外注意:共享库编译选项的影响
即使你用了static或匿名命名空间,也要确保编译共享库时没有强制导出内部符号(比如GCC的-fvisibility=default是默认值,但内部链接符号本来就不会被导出,所以一般不用额外担心)。如果不小心加了-fvisibility=hidden之外的选项,也可能导致符号意外共享,但这种情况很少见。
内容的提问来源于stack exchange,提问作者swang

