如何避免共享库内部调用new触发自身全局重载的new/delete
我在get_seven.cpp中全局重载了new和delete运算符,同时启用了-fvisibility=hidden编译选项,但在本库内部调用new时,仍会触发自定义的重载实现,这并非我期望的结果。我希望实现以下目标:
- 不使用静态链接的
libstdc++ - 保留全局重载的
new/delete定义
请问是否可以通过代码调整或编译选项设置,让可见性隐藏规则生效?
运行命令:./demonstrate.sh
相关代码文件
demonstrate.sh
#!/usr/bin/env sh echo "Without -fvisibility=hidden:" make clean && make && ./test echo echo "With -fvisibility=hidden:" make clean && SO_CFLAGS=-fvisibility=hidden make && ./test #make clean && make && ./test
get_seven.h
// Returns 7 from PublicGetSeven #include <cstddef> //#ifdef __cplusplus //extern "C" { //#endif __attribute__((__visibility__("default"))) int PublicGetSeven(); //__attribute__((__visibility__("hidden"))) void* operator new(size_t nSize); //#ifdef __cplusplus //} //#endif
get_seven.cpp
#include "get_seven.h" #include <iostream> #include "malloc.h" #pragma GCC visibility push(hidden) int internal_do_calculation(); #pragma GCC visibility pop #pragma GCC visibility push(hidden) __attribute__((__visibility__("hidden"))) void* operator new(size_t nSize) { std::cout << "my new called" << std::endl; return malloc(nSize); } void operator delete(void* pMem) { std::cout << "my delete called" << std::endl; free(pMem); } #pragma GCC visibility pop #pragma GCC visibility push(default) int PublicGetSeven() { return internal_do_calculation(); } int internal_do_calculation() { int* n = new int; return 7; } #pragma GCC visibility pop
get_three.h
// get_three.h // Returns 3 from PublicGetThree #ifdef __cplusplus extern "C" { #endif __attribute__((__visibility__("default"))) int PublicGetThree(); #ifdef __cplusplus } #endif
get_three.cpp
#include "get_three.h" #pragma GCC visibility push(hidden) int internal_do_calculation(); int PublicGetThree() { return internal_do_calculation(); } int internal_do_calculation() { int* n = new int; return 3; } #pragma GCC visibility pop
Makefile
CFLAGS+=-g -Wall UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) FINAL_EXE_LDFLAGS+=-Wl,-rpath=. endif ALL: test test: test.cpp libget_three.so libget_seven.so g++ -g $(CFLAGS) $(FINAL_EXE_LDFLAGS) -lstdc++ -o test test.cpp -L. -lget_three -lget_seven -Wl,-Bsymbolic #g++ -g $(CFLAGS) $(FINAL_EXE_LDFLAGS) -lstdc++ -o test test.cpp -L. -lget_three -lget_seven -Wl,-Bsymbolic -static-libstdc++ #g++ $(CFLAGS) $(FINAL_EXE_LDFLAGS) -o test test.cpp -L. -lget_three -lget_seven #-L./glibc-2.27/bld/ -lc #-lc #g++ $(CFLAGS) $(FINAL_EXE_LDFLAGS) -o test test.cpp -L. -lget_three -lget_seven #-L./glibc-2.36/bld libc.a #-lc libget_three.so: get_three.o g++ -o libget_three.so -shared get_three.o -g -Wl,-Bsymbolic #g++ -o libget_three.so -shared get_three.o -g -Wl,-Bsymbolic -static-libstdc++ get_three.o: get_three.cpp get_three.h g++ $(CFLAGS) $(SO_CFLAGS) -o get_three.o -c -fPIC get_three.cpp -g -Wl,-Bsymbolic #g++ $(CFLAGS) $(SO_CFLAGS) -o get_three.o -c -fPIC get_three.cpp -g -Wl,-Bsymbolic -static-libstdc++ libget_seven.so: get_seven.o g++ -o libget_seven.so -shared get_seven.o -g -Wl,-Bsymbolic #g++ -o libget_seven.so -shared get_seven.o -g -Wl,-Bsymbolic -static-libstdc++ get_seven.o: get_seven.cpp get_seven.h g++ $(CFLAGS) $(SO_CFLAGS) -o get_seven.o -c -fPIC get_seven.cpp -g -Wl,-Bsymbolic #g++ $(CFLAGS) $(SO_CFLAGS) -o get_seven.o -c -fPIC get_seven.cpp -g -Wl,-Bsymbolic -static-libstdc++ clean: rm -f test libget_three.so libget_seven.so *.o
test.cpp
#include <stdio.h> #include "get_three.h" #include "get_seven.h" int main() { printf("PublicGetThree returned %d\n", PublicGetThree()); printf("PublicGetSeven returned %d\n", PublicGetSeven()); }
问题本质
你遇到的不是可见性规则失效,而是C++的名字查找机制:当前翻译单元(TU)内定义的operator new/delete重载,会被本TU内的new表达式优先解析调用。-fvisibility=hidden仅控制符号是否对外暴露,不影响内部代码的符号查找逻辑。
可行方案
方案1:显式调用标准库的operator new/delete
在库内部需要使用标准内存分配的代码中,通过**全局作用域运算符::**绕过当前TU的重载,直接调用标准库实现。修改get_seven.cpp中的internal_do_calculation函数:
int internal_do_calculation() { // 显式调用标准库的operator new int* n = static_cast<int*>(::operator new(sizeof(int))); // 若需释放,同样使用全局作用域的operator delete // ::operator delete(n); return 7; }
该方案完全符合你的需求:既保留了对外隐藏的自定义全局重载,又让内部代码使用标准库分配逻辑,且无需静态链接libstdc++。
方案2:将自定义重载放入匿名命名空间
如果自定义重载仅需在特定场景触发(非严格全局重载),可将其放入匿名命名空间,限制作用域为当前TU,内部代码默认调用标准库的new/delete。修改get_seven.cpp的重载部分:
#pragma GCC visibility push(hidden) namespace { __attribute__((__visibility__("hidden"))) void* operator new(size_t nSize) { std::cout << "my new called" << std::endl; return malloc(nSize); } void operator delete(void* pMem) { std::cout << "my delete called" << std::endl; free(pMem); } } #pragma GCC visibility pop
注意:此方案下的重载不再是全局重载,仅能在当前TU内显式调用,若需其他TU调用则不适用。
验证可见性
你当前给operator new添加的__attribute__((__visibility__("hidden")))已经生效,可通过nm -D libget_seven.so查看符号表,自定义operator new会被标记为H(hidden),外部无法解析该符号。
内容的提问来源于stack exchange,提问作者Hemant Kr

