C++ STL 之 allocator 与内存池详解

C++ STL内存管理实战:手把手教你自定义allocator(附性能对比) 本文深入探讨了C++ STL中自定义allocator的实战应用,旨在解决高频小对象分配等场景下std::allocator的性能瓶颈。通过手把手教学,详细解析了allocator_traits的协作机制,并实现了一个简易内存池分配器,最后通过严谨的性能对比验证了自定义allocator在分配速度和减少内存碎片方面的显著优势。 阅读详情

C++ STL 之 allocator 与内存池详解

难度:高级|字数:≈3500|阅读时间:12 min

问题:为什么要有 allocator?

std::vector<int> 可以动态增长,但 new int[n] 不行——因为 vector 需要将内存分配与对象构造解耦。

C++ 的 operator new 每次分配都要从堆上取,频繁小对象分配会产生碎片和巨大的性能开销。allocator 就是 STL 容器的"内存供应商"接口,它让你能替换底层策略,包括内存池。


一、std::allocator —— 默认分配器

标准库每个容器模板都有一个 Allocator 参数:

template <class T, class Allocator = std::allocator<T>>
class vector;

std::allocator<T> 的接口:

template <typename T>
struct allocator {
    using value_type = T;

    T* allocate(std::size_t n) {
        // 默认调用 ::operator new(n * sizeof(T))
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }

    void deallocate(T* p, std::size_t) noexcept {
        ::operator delete(p);
    }

    // C++11 之后,构造/析构被 deprecated,改用 allocator_traits
};

关键细节:allocate 只拿裸内存,不构造对象。构造由容器的 construct 或 std::allocator_traits::construct 通过 placement new 完成:

::new (p) T(std::forward<Args>(args)...);

这个两步走(alloc → construct / destroy → dealloc)是 allocator 设计的核心,也是写内存池的基础。


二、SGI STL 二级分配器(非标,但影响深远)

SGI STL 是 GCC 早期使用的实现,它的 __default_alloc_template 常被面试题称为"SGI 二级分配器"。它不是标准,但思想被无数项目借鉴。

架构

是

否

是

否

是

否

用户请求 n 字节

n > 128 bytes?

一级分配器
调用 malloc

二级分配器
查自由链表

free_list[i]
是否有空闲块?

弹出链表头节点
返回给用户

从内存池
refill 一批块

内存池剩余
是否满足需求?

切割内存池
分配一块给用户
剩余挂入链表

调用 malloc
补充内存池
(若失败则从
更大链表借)

核心设计

  • 16 条自由链表,管理 8、16、24、…、128 字节(8 对齐)
  • 空闲块用嵌入式指针(union)串联——块空闲时存储 next 指针,分配出去后这块内存归用户用,零额外开销
  • 内存池(memory pool)是一大块预分配的连续内存,refill 时从池中切出若干块,多余挂入 free list
  • 内存池耗尽时调用 malloc 重新申请(chunk = 20 块 × 上调后的大小),若失败则向更大的 free list 借
// 伪代码示意
union obj {
    union obj* free_list_link;  // 空闲时用
    char data[1];               // 占位,实际不占用
};

enum { ALIGN = 8, MAX_BYTES = 128, NFREELISTS = 16 };

static obj* free_list[NFREELISTS];
static char* start_free;  // 内存池起始
static char* end_free;    // 内存池结束

核心思想:小对象不走 malloc/free,从池中取,极大降低碎片和系统调用开销。


三、allocator_traits —— C++11 统一接口

C++11 以前,自定义分配器需要实现所有成员类型和函数,非常繁琐。allocator_traits 提供了默认实现,让分配器作者只需实现最少接口。

template <typename Alloc>
struct allocator_traits {
    using allocator_type = Alloc;
    using value_type = typename Alloc::value_type;
    // pointer, const_pointer, void_pointer, size_type, difference_type ...
    // 若 Alloc 未定义,使用默认

    static pointer allocate(Alloc& a, size_type n) {
        return a.allocate(n);
    }

    template <typename U, typename... Args>
    static void construct(Alloc&, U* p, Args&&... args) {
        ::new (p) U(std::forward<Args>(args)...);  // 默认 placement new
    }

    template <typename U>
    static void destroy(Alloc&, U* p) {
        p->~U();
    }
};

这意味着写一个最简单的分配器,只需要提供 value_type、allocate、deallocate,剩下的 construct/destroy 由 traits 补齐。

template <typename T>
struct MyAlloc {
    using value_type = T;
    T* allocate(std::size_t n) { ... }
    void deallocate(T* p, std::size_t n) { ... }
};

// 直接用:construct/destroy 来自 allocator_traits
MyAlloc<int> a;
int* p = std::allocator_traits<MyAlloc<int>>::allocate(a, 10);
std::allocator_traits<MyAlloc<int>>::construct(a, p, 42);
std::allocator_traits<MyAlloc<int>>::destroy(a, p);
std::allocator_traits<MyAlloc<int>>::deallocate(a, p, 10);

四、C++17 PMR —— 多态内存资源

C++17 引入 std::pmr(Polymorphic Memory Resources),这是标准对"替换分配器"问题的终局方案。

PMR 体系

pmr::vector

polymorphic_allocator

memory_resource*
(运行时多态)

new_delete_resource
(默认,调 operator new)

monotonic_buffer_resource
(最快,不回收)

synchronized_pool_resource
(线程安全池)

unsynchronized_pool_resource
(非线程安全池)

向上游资源
申请大块

四种资源详解

资源线程安全回收行为适用场景
new_delete_resource是完整回收兜底,等价于 ::operator new/delete
monotonic_buffer_resource否不回收,析构一次释放全部短期任务、单帧计算
synchronized_pool_resource是按池回收,归还上游多线程容器频繁分配
unsynchronized_pool_resource否按池回收,归还上游单线程高频小对象
monotonic_buffer_resource —— 最快的分配器
// 最快分配器:从不 deallocate,析构一次性释放
char buffer[1024] = {};
std::pmr::monotonic_buffer_resource pool{
    buffer, sizeof(buffer),
    std::pmr::new_delete_resource()  // 上游,buffer 用尽时后备
};

std::pmr::vector<int> vec{&pool};
vec.reserve(100);  // 全部从 buffer 中切,零 malloc

特点:

  • do_allocate:从缓冲区简单偏移,O(1)
  • do_deallocate:直接 no-op
  • 析构时整块释放,免去逐个释放的开销
  • 配合上层一个请求/一个 request 的生命周期使用
pool_resource —— 专门对付小对象

内部维护多个自由链表(与 SGI 相似),按 8 的倍数对齐。不同之处在于它是标准、可组合的:

std::pmr::unsynchronized_pool_resource pool;

std::pmr::vector<std::string> vec{&pool};
for (int i = 0; i < 1000; ++i)
    vec.emplace_back("hello");

// 所有 string 内部的 char 数组分配走池,减少碎片

polymorphic_allocator —— 运行时多态

传统模板分配器的问题是类型绑定不同分配器的容器类型不兼容:

std::vector<int, MyAlloc<int>> v1;
std::vector<int, OtherAlloc<int>> v2;
// v1 = v2;  // Error!不同类型

PMR 把分配器抽象到运行时指针:

using pmr_vector = std::pmr::vector<int>;  // 等价 vector<int, polymorphic_allocator<int>>

pmr_vector v1{&pool1};
pmr_vector v2{&pool2};
v1 = v2;  // OK,底层 memory_resource* 不同但类型相同

整个 std::pmr 容器族(pmr::vector、pmr::string、pmr::map 等)共享同一个分配器类型,全部在运行时切换资源。

实战:用 PMR 监控分配

继承 memory_resource,三行代码实现分配计数:

class TracingResource : public std::pmr::memory_resource {
    std::pmr::memory_resource* upstream_;
    size_t allocated_ = 0;
public:
    explicit TracingResource(std::pmr::memory_resource* up)
        : upstream_(up) {}

    size_t allocated() const { return allocated_; }

private:
    void* do_allocate(size_t bytes, size_t align) override {
        allocated_ += bytes;
        return upstream_->allocate(bytes, align);
    }
    void do_deallocate(void* p, size_t bytes, size_t align) override {
        upstream_->deallocate(p, bytes, align);
    }
    bool do_is_equal(const memory_resource& other) const noexcept override {
        return this == &other;
    }
};

// 使用
TracingResource tracer{std::pmr::new_delete_resource()};
std::pmr::vector<int> v{&tracer};
v.push_back(1);  // tracer.allocated() 会增长

五、面试题精选

Q1:allocator 为什么把 allocate 和 construct 分开?

答:因为未初始化的内存不包含对象。容器需要先拿裸内存,然后在真正插入元素时才构造对象(如 vector 的 reserve 只 allocate,push_back 才 construct)。这也让自定义分配器可以在预分配的大块内存上做 placement new。

Q2:SGI 二级分配器为什么选 128 字节为阈值?

答:经验值。多数小对象(int、指针、小型 POD)在 128 字节内。更大对象直接走 malloc,避免内存池膨胀。128 配合 8 字节对齐正好 16 条链表,管理成本可控。

Q3:PMR 与模板 allocator 的核心区别是什么?

答:模板 allocator 在编译期绑定(不同分配器的容器类型不同),PMR 在运行期绑定(所有 pmr::vector<T> 类型相同,运行时决定走哪个 resource)。前者零开销抽象,后者灵活的运行时多态。

Q4:monotonic_buffer_resource 的 deallocate 是空操作,不会内存泄漏吗?

答:不会。它的设计前提是整个资源对象的生命周期对应一个阶段(如一次网络请求、一帧渲染)。阶段结束时资源对象析构,一次性释放所有内存。这是有意为之——换取分配速度的极致。

Q5:如何让 pmr::vector 使用栈上的缓冲区?

答:给 monotonic_buffer_resource 传一个栈 buffer 数组的指针和大小:

char buf[4096];
std::pmr::monotonic_buffer_resource pool{buf, 4096};
std::pmr::vector<int> v{&pool};
v.reserve(1024);  // 全部从栈上分配,没有 malloc

Q6:如何让自定义分配器兼容 C++11 以下的代码?

答:实现 rebind 机制:

template <typename T>
struct MyAlloc {
    template <typename U>
    struct rebind { using other = MyAlloc<U>; };
};
// 旧版容器通过 rebind<U>::other 获取分配 U 类型的分配器
// C++17 起 rebind 被 allocator_traits 自动推导,不再需要

Q7:std::allocator 在 C++17 之后有什么变化?

答:C++17 弃用了 construct、destroy、max_size 等成员方法(由 traits 提供)。同时 PMR 成为推荐方案,但 std::allocator<T> 仍然是默认分配器,保持向后兼容。

Q8:写一个最少实现的 PMR memory_resource 需要实现几个虚函数?

答:三个纯虚私有函数:do_allocate、do_deallocate、do_is_equal。加上公开的 allocate / deallocate / is_equal 是基类已实现的,子类不露虚函数。


总结

年代方案特点
C++98std::allocator默认方案,包装 ::operator new/delete
实践中SGI 二级分配器非标但影响深远的 16 链表 + 内存池
C++11allocator_traits统一接口,降低自定义成本
C++17PMR运行期多态,4 种资源开箱即用

选择建议:

  • 默认开发:用 std::allocator,不需要动
  • 小对象高频分配,单线程:unsynchronized_pool_resource
  • 多线程高频分配:synchronized_pool_resource
  • 短期大量分配不回收:monotonic_buffer_resource
  • 想监控或者 mock 内存:继承 memory_resource,一行钩子
【源码剖析】MemoryPool —— 简单高效的内存池 allocator 实现 内存池简单说,是为了减少频繁使用 malloc/free new/delete 等系统调用而造成的性能损耗而设计的。当我们的程序需要频繁地申请和释放内存时,频繁地使用内存管理的系统调用可能会造成性能的瓶颈,嗯,是可能,毕竟操作系统的设计也不是盖的。内存池的思想是申请较大的一块内存(不够时继续申请),之后把内存管理放在应用层执行,减少系统调用的开销。 阅读详情

相关推荐

Multisim的探针(Probe)及数字电路的一些调试技巧的应用

Multisim的探针(Probe)及数字电路的一些调试技巧的应用 --EE Bang   By  WRS 上图为555方波发生器,根据R1,R2,C2参数的配置可产生频率为46.3KHZ,占空比50%的方波信号,如果用示波器查看波形参数的话需要用两个坐标轴做差,操作不便,另外如果要修改电路将上述电阻换成滑动变阻器的话,使其产生可变频率

Agent Christmas的博客 3万+

续STL之allocator

针对CPP的标准模版库的空间配置器而写的文章,通过实现一个自己的配置器了解容器底层空间操作原理

thirtythird的博客 525

深入解析ST MC SDK(WorkBench)工程结构:从零搭建FOC电机控制项目

本文深入解析了ST MC SDK(WorkBench)工程结构,详细介绍了如何从零搭建FOC电机控制项目。通过ST MC SDK和WorkBench工具链,开发者可以快速配置电机类型、控制策略等参数,生成完整的FOC控制工程。文章还拆解了工程结构中的核心控制模块、HAL硬件抽象层、芯片系统初始化和电机控制算法库,并提供了无传感器FOC实战技巧和常见问题排查指南,帮助开发者高效掌握STM32F407ZGT6开发板上的FOC电机控制技术。

weixin_29173777的博客 412

C++ 简单内存池的设计与STL中分配器的设计

【代码】C++ 简单内存池的设计与STL中分配器的设计。

saber520___的博客 205

BK7238(XH-WB3S)固件烧录实战:从AT固件选择到稳定下载全解析

本文详细解析了BK7238(XH-WB3S)模组固件烧录的全过程,从AT固件版本选择到稳定下载的实战技巧。重点介绍了烧录前的准备工作、硬件连接的正确姿势、烧录软件的实战技巧以及疑难问题排查指南,帮助开发者高效完成BK7238模组的固件烧录。

weixin_29159441的博客 253

在 Ansys Mechanical 中使用“螺栓工具”插件自动生成螺栓

在有限元分析 (FEA) 中,高效创建螺栓连接对于确保机械装配的结构完整性和性能至关重要。螺栓是连接组件不可或缺的一部分,它们在负载下的精确建模会影响整个系统。快速高效的螺栓建模使工程师能够快速优化设计,满足紧迫的期限,并通过为其他关键设计任务释放资源来提高整体生产力。此外,螺栓建模的精度可以最大限度地降低模型创建过程中出错的风险,这对于获得可靠和准确的分析结果至关重要。因此,在 FEA 中快速且无错误地创建螺栓连接对于高效、可靠和成功的工程结果至关重要。

jkh920184196的博客 3102

C++ 内存分配器-allocator

vectorSTL 中负责内存分配的核心是,核心能力是“分离内存分配与对象构造”,为容器提供高效的内存管理。它是所有 STL 容器的默认内存分配器,底层依赖全局,但包装了更灵活的接口。实际开发中,若需优化内存碎片、提升多线程性能,可替换为自定义分配器(如内存池分配器)或 C++17+ 的。STL 内存分配找allocator,核心是“分离分配与构造”。

XiaoBinbest的博客 1517

x2-DFD:A framework for explainable and explainable deepfake detection

该文章提出了X2-DFD框架,用于可解释和可扩展的深度伪造检测。通过评估大型多模态语言模型(MLLMs)在深度伪造检测中的内在能力,发现它们在不同伪造相关特征上的有效性各异。基于此,实施了针对性的微调策略,显著提高了MLLMs的解释能力,并整合了外部深度伪造检测器(EDDs),以结合MLLMs和传统检测器的互补优势,实现更好的检测和解释。

志在山顶的人,不会贪念山腰的风景 510

C++ STL allocator设计原理与性能优化实践

内存管理是C++高性能编程的核心技术之一,STL allocator作为容器与内存管理的桥梁,通过分离内存分配策略与数据结构实现,提供了极大的灵活性。其核心原理包括原始内存分配、对象构造解耦以及rebind机制,使得开发者可以针对特定场景(如高频交易、共享内存通信)实现定制化内存管理方案。通过内存池、无锁分配等优化技术,自定义allocator能显著提升程序性能,实测在频繁分配小对象场景下性能提升可达300%。理解allocator的工作机制,有助于开发者设计更高效的C++内存管理策略。

weixin_32175665的博客 42

2万字长文:海豚调度器(DolphinScheduler)面试题深入了解

DAG图(有向无环图)是海豚调度器中表示工作流任务依赖关系的一种方式。在DolphinScheduler中,每个任务是一个节点,任务之间的依赖关系通过有向边连接这些节点来表示。无环:保证了工作流的执行顺序是确定的,避免了无限循环的问题。有向:箭头指示了任务的执行方向,即上一个任务的输出可以作为下一个任务的输入。依赖表示:通过图形化界面,用户可以通过拖拽连接线来定义任务之间的先后顺序和依赖关系。并行处理:DAG图可以表示出任务的并行关系,允许多个任务在没有直接依赖关系的情况下同时执行。条件分支。

大模型大数据攻城狮的专栏 1333

如何用Python编程实现算术割圆术?

第10章 割圆术 Python编程要点 from … import …语句 math模块的sqrt()函数 round()函数 10.1 图解九章 {:–}觚( gū ):正多边形。“六觚”即正六边形,“十二觚”即正十二边形。 图10-1 六觚、十二觚、二十四觚 提示 图10-1所示由图解九章源码中的“图解割圆术.py”自动绘制而成,我们可运行脚本文件,查看动态的绘制过程。 割六觚以...

人邮异步社区 3237

终极指南:gh_mirrors/st/STL内存管理机制深度剖析——allocator设计与优化策略

MSVC's implementation of the C++ Standard Library(gh_mirrors/st/STL)是一个高效的C++标准库实现,其内存管理机制是保证性能的核心。本文将深入剖析STL中的allocator设计与优化策略,帮助开发者更好地理解和使用STL容器。 ## 什么是allocator?allocator的核心作用 allocator是C++标准库中的内

gitblog_01059的博客 433

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

STL源码剖析---------SGI空间配置器

书中说到:delete应该是调用对象的析构函数,然后释放内存;,destory也是调用对象的析构函数,vector的析构函数就是先调用了destory,调用对象类型的析构函数,然后调用deallocate释放内存;书上的解释是只是将delete的操作分开了;先是调用析构函数,然后释放内存;记录一下。 STL allocator将new和delete进行了分开操作: 内存分配操作由alloc::...

qq_41983709的博客 211

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

移动语义与容器极致优化,emplace/push底层差异、对象复用、std::allocator原理、自定义STL分配器实战

我们彻底吃透了内存池体系,解决了系统堆频繁分配慢、内存碎片、多线程锁竞争等底层内存问题,掌握了定长内存池、对象池、线程安全池的手写实现与工业级分配器选型。内存池解决的是操作系统层级的内存分配效率,而今天我们要解决的是C++ 业务层、容器层的对象构造开销。绝大多数开发者使用 STL 容器常年存在隐形性能损耗:1. 分不清push_back 与 emplace_back本质差异,频繁产生临时对象、拷贝冗余;2. 不理解移动语义触发规则,本该零开销转移对象,却触发昂贵深拷贝;

2401_86195313的博客 377

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

【c++】【stl】std::allocator(vector 默认分配器)

内存池优化:频繁小内存分配(比如 vector 频繁扩容)时,内存池可减少new/delete的系统调用开销;对齐要求:某些硬件 / 算法要求内存按 16/32 字节对齐,仅保证基本对齐;调试 / 统计:自定义分配器可统计内存分配次数、大小,排查内存泄漏;共享内存:分配器可从共享内存区域分配内存,实现进程间数据共享。std::allocator 核心职责:分离 “内存分配 / 释放” 和 “元素构造 / 析构”,vector 的所有内存操作都通过它完成;核心接口:处理原始内存(底层。

2402_85646905的博客 750

C++的std--allocator_traits分配器特性与自定义内存管理的适配

传统分配器需实现allocate、deallocate等完整接口,而通过allocator_traits,即使分配器类未实现某些方法(如construct/destroy),也能自动提供默认实现。std::allocator_traits作为C++11引入的分配器特性模板,为自定义内存管理提供了统一的适配接口,让开发者能在不重写整套分配逻辑的情况下,灵活扩展内存管理策略。通过allocator_traits适配自定义内存池时,可重载allocate与deallocate,将其指向预分配的内存块。

yxeukf_933的博客 49

深入理解C++ STL分配器:从内存管理原理到高性能自定义实现

内存管理是C++编程的核心基础,它直接影响程序的性能和稳定性。其核心原理在于将内存分配与对象构造分离,这一设计使得容器能够高效地管理内存生命周期。在工程实践中,默认的通用分配器在高频次小对象分配或高并发场景下可能成为性能瓶颈,导致内存碎片和锁竞争问题。为了解决这些挑战,开发者可以基于内存池、自由链表等数据结构实现自定义分配器,例如针对固定大小对象的池分配器或借鉴SGI STL的二级分配器设计。这些优化技术特别适用于网络服务器、游戏引擎、高频交易等对性能敏感的应用场景。本文通过解析STL分配器的接口设计与实现

weixin_34127717的博客 309

STL容器内存管理与优化策略详解

STL(标准模板库)容器是C++编程中的核心组件,其高效的内存管理策略直接影响程序性能。通过模板实现的类型安全、内存预分配机制和迭代器抽象,STL在保证通用性的同时优化了运行效率。以vector为例,其动态扩容机制采用2倍增长策略,而deque则通过分块连续存储实现首尾高效插入。内存分配器作为底层支撑,默认使用全局new/delete,但通过自定义内存池可显著提升性能。在现代C++中,移动语义和小对象优化技术进一步减少了内存拷贝开销。这些设计使得STL容器特别适合处理大规模数据、高频插入删除等场景,是构建高

weixin_30225755的博客 256

C++ STL源码深度剖析:六大组件与核心容器工程权衡

泛型编程是现代C++高效性的基石,STL通过容器、迭代器、算法等六大组件实现了数据存储与操作的彻底解耦,其中迭代器作为桥梁让算法可以复用而无需感知具体容器结构。在工程实践中,深入理解vector扩容策略、红黑树的有序映射、哈希表的冲突处理以及内存池分配器的工作原理,能显著提升程序性能并有效规避迭代器失效等经典陷阱。本文从libstdc++源码视角出发,剖析经典容器的底层内存布局与关键算法的工程取舍,揭示每个设计背后的复杂度契约与性能平衡,为阅读和应用STL提供一套系统的方法论,帮助开发者从“API记忆”走向

weixin_28726671的博客 266

C++内存池实现指南:从小对象优化到通用池与并发设计

在高并发服务中,高频小对象的创建与销毁往往让malloc/free成为性能瓶颈,其堆管理、碎片化以及多线程锁竞争都会造成可观的CPU开销。内存池通过一次性向系统申请大块内存并自行维护空闲链表,将分配路径压缩为指针操作,从根本上减少系统调用与锁竞争,显著提升吞吐。针对不同对象大小,工程上常采用定长池与按尺寸分类的通用池设计,并结合线程局部缓存与对齐控制确保多线程安全与内存正确性。同时,C++内存池还能通过自定义allocator接入STL容器,让业务代码透明受益。本文从实际性能排查出发,手写实现一个可用的池化

weixin_33428613的博客 264
上一篇: C++ STL之array、pair与tuple详解:从使用到底层,再到面试八股
下一篇: LangChain Tools 生态:内置工具集、自定义工具、Toolkits
Ricky_Theseus
博客等级 码龄3年 127粉丝 158原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ricky_Theseus

感谢大家,祝您生活愉快

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值