标记为extern的全局变量重复问题排查与解决咨询
问题背景
我在链接共享库时,该库声明了如下全局变量:
__attribute__((visibility("hidden"))) extern HookList<MallocHook::DeleteHook> delete_hooks_;
该变量的声明位于src/malloc_hook-inl.h:122,定义位于src/malloc_hook.cc:240。
尽管变量被声明为extern,却出现了重复实例。单次执行中,调试main.cpp第7、8行时,LLDB显示delete_hooks_来自不同地址空间:
栈追踪1(主二进制程序Example的地址空间)
this == 0x5574f1215968 base::internal::HookList<void (*)(const void *)>::Add(void (*)(const void *)) malloc_hook.cc:155 ::MallocHook_AddDeleteHook(MallocHook_DeleteHook) malloc_hook.cc:265 MallocHook::AddDeleteHook(void (*)(const void *)) malloc_hook.h:115 ::HeapProfilerStart(const char *) heap-profiler.cc:454 main main.cpp:7
栈追踪2(libtcmalloc.so的地址空间)
this == 0x7efe998baa28 base::internal::HookList<void (*)(const void *)>::empty() const malloc_hook-inl.h:88 [Inlined] free_fast_path(void *) tcmalloc.cc:1944 ::tc_free(void *) tcmalloc.cc:1953 main main.cpp:8
我的问题
- 为什么该变量会重复?
extern关键字不是应该防止这种情况吗? - 如何避免这种重复?
- 我是否误用了该库?我认为已严格遵循官方文档指引。
相关环境与代码信息
- 系统:Ubuntu 20.04,LLVM工具链19.0.0
- CMake编译选项:
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=/usr/bin/clang-19 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-19 - gperftools版本:2.15(源码来自gperftools仓库)
CMakeLists.txt
# CMakeLists.txt cmake_minimum_required(VERSION 3.28) # likely compatible with an older version too project(Example LANGUAGES CXX) add_executable(Example main.cpp) set(gperftools_build_benchmark OFF CACHE BOOL "Build gperftools benchmark" FORCE) set(PATH_TO_GPERFTOOLS "${CMAKE_SOURCE_DIR}/gperftools") add_subdirectory(${PATH_TO_GPERFTOOLS}) target_link_libraries(Example tcmalloc) target_include_directories(Example PRIVATE ${PATH_TO_GPERFTOOLS}/src)
main.cpp
// main.cpp #include <cstdlib> #include "gperftools/heap-profiler.h" int main() { HeapProfilerStart("/tmp/example"); // stacktrace #1 std::free(nullptr); // stacktrace #2 HeapProfilerStop(); return 0; }
解答
1. 变量重复的原因
核心问题是**visibility("hidden")属性加上编译链接方式导致的符号隔离**:
extern仅声明变量定义在别处,但__attribute__((visibility("hidden")))会让该符号仅在当前编译单元所在的二进制(主程序或共享库)内可见,无法跨二进制共享。- 你直接把gperftools源码作为子目录编译时,主程序
Example包含malloc_hook-inl.h后,会因hidden可见性生成该变量的本地实例;而libtcmalloc.so编译时同样处理这个头文件,生成另一个独立实例。 - 主程序调用
HeapProfilerStart时操作的是自身的delete_hooks_实例,std::free调用tcmalloc代码时则操作共享库内的实例,因此出现两个不同地址。
2. 避免重复的方法
有两种可行思路:
方法一:使用系统预编译的gperftools库
通过包管理器安装gperftools(如sudo apt install libgoogle-perftools-dev),然后在CMake中链接系统库:
find_package(gperftools REQUIRED) target_link_libraries(Example gperftools::tcmalloc)
这种方式下主程序只会链接预编译共享库,不会在本地生成符号实例。
方法二:修改编译配置,确保符号跨二进制可见
若必须从源码编译,需调整gperftools的可见性设置:
set(gperftools_build_benchmark OFF CACHE BOOL "Build gperftools benchmark" FORCE) set(PATH_TO_GPERFTOOLS "${CMAKE_SOURCE_DIR}/gperftools") # 全局设置符号可见性为default,覆盖hidden默认值 set(CMAKE_CXX_VISIBILITY_PRESET default) set(CMAKE_C_VISIBILITY_PRESET default) set(CMAKE_VISIBILITY_INLINES_HIDDEN OFF) add_subdirectory(${PATH_TO_GPERFTOOLS}) target_link_libraries(Example tcmalloc) target_include_directories(Example PRIVATE ${PATH_TO_GPERFTOOLS}/src)
这样delete_hooks_会变为全局可见,主程序和共享库将共享同一个实例。
3. 是否误用了库?
不算误用,但你的编译方式不符合gperftools的预期场景:
- 官方文档默认用户链接预编译共享库,而非直接嵌入源码编译。直接嵌入源码会触发头文件内的
hidden可见性规则,导致符号隔离问题。 - 只要调整编译链接方式,按预期使用预编译库或修正可见性设置,即可正常使用。
内容的提问来源于stack exchange,提问作者Volodymyr Lashko
相关产品推荐
相关产品推荐

