You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免共享库内部调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 03:15:43