为何双指针与shared_ptr配合的违规跨语言代码可运行?
这段代码本质上完全不合理,能“正常运行”只是巧合,属于典型的未定义行为,但在多个平台上居然都能输出预期结果,下面来拆解原因:
涉及代码
C++共享库代码
#include <memory> extern "C" { int make(std::shared_ptr<int> p) { p = std::make_shared<int>(42); return 0; } int get(std::shared_ptr<int> p) { return *p; } }
Python调用代码
import ctypes lib = ctypes.CDLL('lib.so') p = ctypes.c_void_p() lib.make(ctypes.byref(p)) print(lib.get(ctypes.byref(p)))
这段代码在macOS/ARM64(Clang)、Linux/ARM32(GCC)、Windows/AMD64(MSVC)平台编译运行后,都能输出42。
背后的巧合逻辑
之所以能“运行”,是因为当前这些平台的编译器和标准库实现刚好满足以下几个巧合条件:
- std::shared_ptr的内存布局巧合:这些平台的
std::shared_ptr实现中,指向对象的原始指针是其内存布局的第一个成员,Python传递的c_void_p刚好能接住这个指针值。 - 参数传递的巧合:编译器为了优化,对
std::shared_ptr参数选择了按引用传递(而非值传递),避免了shared_ptr的拷贝(也就不会触发引用计数的增减)。 - 内存未被立即回收:
make函数中创建的shared_ptr是栈上临时对象,函数返回后会被销毁,对应的控制块和对象内存理论上已经被释放,但由于小对象内存池/页面未被立即解除映射,这块内存暂时还能被访问,属于**UAF(野指针访问)**行为。 - get函数的行为巧合:同样因为按引用传递,get函数不需要修改引用计数,只是通过拿到的指针做双重解引用,刚好访问到还没被覆盖的内存区域,从而得到
42。
注:原始代码中不存在UAF问题,因为shared_ptr来自生命周期更长的结构,这个简化版本的情况比原始代码更糟。
内容的提问来源于stack exchange,提问作者Masklinn
相关产品推荐
相关产品推荐

