Valgrind 并发调试 :用 Helgrind 抓住线程里的“看不见的错”

Valgrind的隐秘角落:那些不为人知的调试技巧与实战陷阱 本文深入探讨了Valgrind内存调试工具的高级应用与实战技巧。文章超越了基础的内存泄漏检查,详细解析了Memcheck的精细化调优、Helgrind的多线程数据竞争检测、以及处理动态库和复杂IPC场景的挑战。同时,介绍了如何将Valgrind与GDB、Callgrind等工具结合,构建强大的动态调试与性能分析环境,并分享了在生产环境及CI/CD中集成Valgrind的最佳实践。 阅读详情

📖 针对Valgrind 并发调试 :同步B站的视频课程讲解


Valgrind 并发调试 :用 Helgrind 抓住线程里的“看不见的错”

目标:用最小示例快速理解 Valgrind Helgrind 的作用与价值,掌握一套可复用的检查流程。下一篇会单讲 DRD 并与 Helgrind 做更系统对比。


在这里插入图片描述

1. Helgrind 是什么?

Helgrind 是 Valgrind 的线程错误检测器,专注发现:

  • 数据竞争(Data Race):多线程对同一内存的未同步读/写、写/写。
  • 锁使用错误:忘记解锁、重复加锁、加锁顺序反转、潜在死锁提示等。
  • 条件变量/线程 API 误用:如 pthread_cond_wait() 未持锁、线程退出仍持锁等。

一句话:普通运行“看起来没事”的并发 bug,Helgrind 能第一时间指出来。


2. 最小可复现示例(Data Race)

下面这段代码存在竞态:两线程未加锁同时递增全局变量 counter。

// test_race.c
#include <pthread.h>
#include <stdio.h>

int counter = 0;  // 共享变量

void* inc(void* arg) {
    for (int i = 0; i < 10000; i++) {
        counter++;  // 无保护的共享写(有 data race)
    }
    return NULL;
}

int main(void) {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, inc, NULL);
    pthread_create(&t2, NULL, inc, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("counter = %d\n", counter);
    return 0;
}

编译与运行

gcc -O0 -g test_race.c -pthread -o test_race
./test_race

常见输出:

counter = 20000

误导性很强:结果看似正确,但只是“恰好没错”。竞态是非确定性的,随调度变化。

用 Helgrind 检查

valgrind --tool=helgrind ./test_race

你会看到类似:

  • Possible data race during read/write …
  • Address … inside data symbol “counter”

解读要点:

  • Helgrind 抓到了 counter 的读-写/写-写冲突。
  • Locks held: none 表示访问时没有任何互斥保护。

3. 两种正确修法(对照演示)

3.1 用互斥锁(通用)

// test_race_mutex.c
#include <pthread.h>
#include <stdio.h>

static int counter = 0;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void* inc(void* arg) {
    for (int i = 0; i < 10000; i++) {
        pthread_mutex_lock(&lock);
        counter++;                 // 受保护的共享访问
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main(void) {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, inc, NULL);
    pthread_create(&t2, NULL, inc, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("counter = %d\n", counter);
    return 0;
}

编译/检查:

gcc -O0 -g test_race_mutex.c -pthread -o test_race_mutex
valgrind --tool=helgrind ./test_race_mutex    # 应无数据竞争报告

3.2 用原子操作(高效的单变量计数)

// test_race_atomic.c
#include <pthread.h>
#include <stdio.h>
#include <stdatomic.h>

static _Atomic int counter = 0;

void* inc(void* arg) {
    for (int i = 0; i < 10000; i++) {
        atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed);
    }
    return NULL;
}

int main(void) {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, inc, NULL);
    pthread_create(&t2, NULL, inc, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("counter = %d\n", atomic_load_explicit(&counter, memory_order_relaxed));
    return 0;
}

编译/检查:

gcc -O0 -g test_race_atomic.c -std=c11 -pthread -o test_race_atomic
valgrind --tool=helgrind ./test_race_atomic   # 应无数据竞争报告

备注:memory_order_relaxed 足以确保计数正确;如果你的逻辑需要跨变量的可见性顺序,再考虑 acquire/release/seq_cst。


4. Helgrind 常见高价值告警

  • Possible data race:未同步共享访问。
  • Exiting thread still holds N lock(s):线程退出时仍持锁(泄漏或死锁形态)。
  • Lock order violation / potential deadlock:加锁顺序反转,存在环路等待的风险。
  • pthread_cond_wait misuse:pthread_cond_wait()/signal() 使用顺序/持锁条件不正确。

快速定位技巧

  • 使用 -O0 -g 编译,保证行号准确。
  • 先最小化复现(减少线程/数据),再逐步扩大范围。
  • 有“良性竞态”(如自旋标志)时,优先改用原子语义;实在需要再用 suppressions 降噪。

5. Helgrind 与 DRD:先给一个小对比

维度HelgrindDRD
定位严格并发错误分析轻量线程错误检测
数据竞争检测强,误报少中,更快更直观
死锁/锁序提示有,偏“概要/上下文级”直观,且会统计大量实例
性能较慢较快

实战建议:先用 Helgrind 清理数据竞争/锁误用,再用 DRD 看死锁与冲突密度。 下一篇将系统讲解 DRD,并与 Helgrind做实测对比。


6. 一页“上手清单”

  • 编译:-O0 -g;必要时 -std=c11 以支持 <stdatomic.h>。

  • 运行:valgrind --tool=helgrind ./your_app

  • 改法优先级:

    1. 单变量共享:原子;
    2. 多步骤临界区:互斥锁;
    3. 有顺序依赖:合理使用 acquire/release/seq_cst;
    4. 锁顺序统一或 trylock + 退避 解决死锁。
  • CI 体检:把 helgrind 跑进回归测试,阻断“偶发”的并发回归。


7. 结语

  • Helgrind 能把“看不见”的竞态、锁误用、潜在死锁可视化;
  • 配合最小化示例,你能快速判断问题本质:该用 原子 还是 互斥;
  • 下一篇:DRD 实战 —— 更轻量的巡检、死锁与锁序反转的直观诊断、与 Helgrind 的互补使用法。


📖 针对Valgrind 并发调试 :同步B站的视频课程讲解


C++多线程死锁:从原理到实战的预防与诊断指南 在多线程编程中,死锁是一种常见且棘手的问题,它发生在多个线程因竞争资源而陷入相互等待的僵局。其核心原理基于互斥、请求与保持、不剥夺和循环等待四个必要条件。理解这些条件对于设计健壮的并发系统至关重要,能有效避免程序出现无响应的“卡死”现象。在C++工程实践中,通过锁顺序一致性、使用`std::lock`等工具可以预防死锁,而借助`std::mutex`和`std::condition_variable`等同步原语时,也需警惕嵌套调用和回调函数等隐蔽场景。掌握死锁的检测与诊断方法,如使用`gdb`分析线程栈或工 阅读详情

相关推荐

2025 C++并发编程实战:从线程安全到无锁数据结构与协程应用

并发编程是现代软件开发中提升性能的核心技术,其核心原理在于通过多线程或多进程同时执行任务,以充分利用多核CPU的计算能力。从技术价值看,它能显著提高程序的吞吐量和响应速度,尤其适用于高并发服务器、数据处理和实时系统等场景。在C++中,实现并发需要深入理解线程生命周期管理、同步机制和内存模型等基础概念。本文聚焦于C++20/23的最新特性,通过实战案例解析如何构建健壮的线程安全队列,并探讨无锁数据结构与协程的应用。针对常见的性能瓶颈,如锁竞争和缓存伪共享,提供了具体的优化策略和调试方法,帮助开发者从入门到精通

weixin_34411563的博客 347

Helgrind:线程错误检测器

Helgrind是一个Valgrind工具,用于在使用POSIX pthreads线程原语的C、c++和Fortran程序中检测同步错误。POSIX pthread中的主要抽象是:一组共享公共地址空间的线程、线程创建、线程连接、线程退出、互斥锁(锁)、条件变量(线程间事件通知)、读写锁、自旋锁、信号量和屏障。这类问题通常会导致不可复制的、依赖于时间的崩溃、死锁和其他不当行为,而且很难通过其他方法发现。Helgrind知道所有的pthread抽象,并尽可能准确地跟踪它们的效果。

weixin_43402206的博客 1377

C++高效调试实战:从思维到工具的9大技巧与疑难场景解析

调试是软件开发中定位和修复程序错误的核心环节,其本质是基于证据的系统性侦查过程,而非盲目试错。高效的调试依赖于科学的思维方法(如假设驱动、二分法)与现代化工具链的有机结合,旨在快速定位问题根源,提升开发效率。在C++这类系统级编程中,调试尤其关键,因其涉及内存管理、多线程、模板等复杂特性,传统打印调试效率低下。通过掌握条件断点、调用栈分析、内存查看、多线程调试、核心转储分析等核心技术,并结合AddressSanitizer、性能剖析器等工具,开发者能系统性地应对内存泄漏、竞态条件、性能瓶颈等工程挑战。这些方

weixin_34318272的博客 493

Linux多线程调试之 利器Valgrind之helgrind

Valgrind的多线程调试工具   Helgrind是Valgrind的一个重点功能 本节主要针对与多线程基本安全问题进行检测:【所有的代码环境都是在POSIX_THREAD模式下】 写线程代码时 经常碰到如下问题 1)  资源不安全访问 【就是多个线程在没有同步的情况下写某个资源体】 2) 死锁问题  3)  POSIX pthreads API的

sfwtoms11的专栏 5519

深入理解互斥量:多线程同步的核心机制与实战解析

在多线程编程中,共享数据的并发访问极易引发竞态条件,导致结果不可预测。互斥量作为最基础的线程同步原语,通过保证临界区代码的原子性,将并发访问转化为有序排队,从而确保数据一致性。理解其底层原理与正确用法,是构建稳定高效并发系统的关键。在实际工程中,无论是保护全局计数器、维护日志缓冲区,还是实现用户状态统计,互斥量都扮演着不可或缺的角色。然而,锁的粒度控制、死锁避免及性能权衡,往往比API调用本身更具挑战。本文从竞态条件出发,系统剖析互斥量的生命周期、属性配置与常见陷阱,并对比读写锁、条件变量等同步工具,帮助开

weixin_33843409的博客 378

Bouncy Bolson:轻量级2D刚体物理内核原理与实战

刚体动力学是游戏开发、机器人仿真和嵌入式运动控制的基础技术,其核心在于牛顿力学建模、冲量-动量定理求解与碰撞响应的真实性。Bouncy Bolson 以 header-only C++ 实现了完整的 2D 刚体模拟闭环,聚焦 restitution(恢复系数)与 friction(摩擦力)的逐体建模,通过显式欧拉积分、AABB/圆形混合碰撞检测及可纸笔验证的冲量解析算法,在300行内达成高可控性与强可调试性。它不追求功能完备,而提供从质心运动、法向反弹到切向耗散的全链路物理语义暴露,天然适配教育学习、算法验

weixin_30414305的博客 389

Helgrind工具的介绍

Helgrind是Valgrind工具套件中的一个工具,专门用于检测使用POSIX pthreads线程原语的C、C++和Fortran程序中的同步错误。它主要检测以下三类错误:POSIX pthreads API的误用:包括对互斥锁(mutexes)、条件变量等的不当操作。由于锁顺序问题导致的潜在死锁:通过监控线程获取锁...

weixin_42022384的博客 412

【linux】Valgrind工具集详解(十三):Helgrind(线程错误检测器)

Helgrind是一个Valgrind工具,用于检测使用POSIX pthreads线程原语的C,C ++和Fortran程序中的同步错误。 POSIX pthreads中的主要抽象是:共享公共地址空间的一组线程,线程创建,线程连接,线程退出,互斥(锁),条件变量(线程间事件通知),读写器锁,自旋锁,信号量和障碍。 Helgrind可以检测到三类错误,这些错误将在接下来的三个部分中详细讨论: 错误...

郭老二 2895

Valgrind 并发调试 ·:用 Helgrind 抓住线程里的“看不见的错”

本文介绍了Valgrind工具集中的Helgrind线程错误检测器,通过最小示例演示如何检测多线程程序中的并发错误。文章首先展示了一个存在数据竞争(Data Race)的代码示例,该程序在多线程环境下未加锁地修改共享变量,虽然运行结果看似正确,但存在潜在问题。随后分别给出使用互斥锁和原子操作两种修复方案,并通过Helgrind验证修复效果。文章还对比了Helgrind与DRD工具的特点,建议先用Helgrind检测数据竞争和锁误用,再用DRD检查死锁。最后提供了快速上手清单,包括编译选项、运行命令和修复优先

Interview_TC的博客 1072

使用Helgrind进行并发问题的调试

Helgrind是一个非常实用的并发问题检测工具,它可以帮助我们在编程初期就发现并解决并发问题,从而提高代码的质量和稳定性。然而,理解和使用Helgrind需要一定的并发编程知识和经验,因此,在使用Helgrind的过程中,不断学习和实践并发编程是非常重要的。同时,我们也需要明白,工具只是辅助,最终提高代码质量的关键还在于我们的编程能力和技巧。希望通过这篇文章,你可以更有效地使用Helgrind,以及更好地理解并发编程。

夏天的狂风的专栏 464

彻底清理Easy Connect残留:从服务到驱动的完整卸载指南

在企业网络接入场景中,远程客户端常因内核驱动、系统服务和网络过滤组件导致卸载残留,进程无法结束、文件被锁定、注册表项顽固存在。其原理在于驱动挂载网络协议栈,服务守护进程,卸载时资源占用导致清理不彻底。彻底清理的技术价值在于恢复系统纯净、避免网络异常和软件冲突。典型应用场景包括企业办公机、个人笔记本更换或重装客户端。本文针对深信服Easy Connect,提供从停止服务、删除驱动到清理注册表和虚拟网卡的分步操作链路,并给出验证方法和避坑经验。

weixin_31292729的博客 242

C++多线程调试利器:Helgrind与DRD实战指南

在多线程编程中,数据竞争和死锁是两大核心挑战,它们源于多个线程对共享资源的无序访问。数据竞争发生在多个线程未同步地读写同一内存区域,导致未定义行为;而死锁则因线程间循环等待锁资源而引发程序停滞。这些并发问题的隐蔽性强,难以通过传统调试工具复现和定位。Valgrind框架下的Helgrind和DRD工具,通过运行时动态插桩技术,能够精准检测数据竞争、锁顺序错误等线程同步问题,为C++多线程程序的稳定性和正确性提供了关键保障。在开发实践中,合理运用这些工具,结合原子操作与互斥锁等同步机制,能有效提升高并发场景下

weixin_34060299的博客 398

实战分享:如何用Jetson Nano+HM30图传搭建低空YOLOv5检测系统(附VNC避坑指南)

本文详细介绍了如何利用Jetson Nano和HM30图传搭建低空YOLOv5检测系统,重点解决了无人机应用中的硬件连接和VNC延迟优化问题。通过对比WiFi直连与图传方案的优劣,提供了网络配置、VNC优化及YOLOv5模型加速的实战技巧,帮助开发者实现高效稳定的低空目标检测系统。

weixin_29174385的博客 110

Rust 工程实践:用 Miri、Valgrind 与 LLVM Sanitizers 验证 unsafe 代码

> 本文是 RustTraining 仓库中 [Rust Engineering Practices](https://link.gitcode.com/i/dacfaf874edaef000332761ffb91cfe0) 系列第 5 章《Miri, Valgrind, and Sanitizers — Verifying Unsafe Code》的完整技术解读。Safe Rust 在编译期保证

gitblog_00667的博客 539

RedPitaya火龙果板基础操作

火龙果板基础操作一级目录二级目录三级目录 一级目录 二级目录 三级目录

qq_41523340的博客 9723

【Docker】从零构建达梦DM8轻量级服务镜像:实战与优化

本文详细介绍了从零开始构建达梦DM8轻量级Docker镜像的完整实战流程。通过分析传统安装方式的痛点,阐述了使用Docker封装数据库的优势,并重点讲解了如何利用静默安装、精简组件、多阶段构建等优化技巧,打造一个体积小巧、启动迅速、配置灵活的DM8服务镜像,助力实现开发、测试与部署的环境一致性与高效性。

ooo22的博客 520

数据结构-内存调试工具

是 Linux 下 C/C++ 语言最常用的。gcc 负责把 C 代码编译成可执行程序;。

2401_82879595的博客 202

K8s(二十一):在 Kubernetes 集群中部署 MySQL8.0 高可用集群(1主2从)

🔴 K8s(二十一):在 Kubernetes 集群中部署 MySQL8.0 高可用集群(1主2从)

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 3万+

Linux下C++开发实战:从环境搭建到调试优化的完整指南

C++作为高性能系统编程的核心语言,其编译链接机制是理解程序构建过程的基础。编译器将源代码转换为目标文件,链接器则负责解析符号引用并生成可执行文件,这一分离式设计带来了灵活性的同时也引入了依赖管理的复杂性。在Linux生态中,GCC和Clang工具链的标准化与碎片化并存,使得掌握头文件路径、库文件链接及ABI兼容性成为工程实践的关键技能。通过CMake等构建系统管理项目依赖,结合GDB/Valgrind进行调试与内存检测,开发者能够构建稳定高效的C++应用。本文聚焦Linux环境下C++开发的常见痛点,提供

diaohuyi6830的博客 421

安全监控:人员行为分析_(2).人员行为分析基础理论

人员行为分析是指利用计算机视觉技术对视频中的人类行为进行实时或离线检测、识别和理解的过程。视频采集:通过摄像头等设备获取视频数据。视频预处理:对视频数据进行噪声过滤、图像增强等处理,以便后续分析。特征提取:从视频中提取出与人员行为相关的特征,如人体姿势、动作轨迹等。行为识别:利用机器学习或深度学习模型对提取的特征进行分类,识别出具体的人员行为。行为理解:对识别出的行为进行进一步分析,理解其背后的意图或状态。在人员行为分析中,常用的公开数据集包括UCF101、HMDB51、ActivityNet等。

zhubeibei168的博客 1345

把 ABAP writable CDS view entity 讲透:从只读建模到可写模型的 ABAP CDS 新能力

摘要:SAP引入的writable CDS view entity突破了传统CDS视图只读的限制,允许通过ABAP SQL直接进行数据修改。该特性通过WRITABLE关键字定义,使CDS视图成为与数据库表同等级别的可写对象。其核心价值在于为数据集成、中间层同步等场景提供结构化写入入口,但需注意不应绕过业务规则。最佳实践建议保持底层表结构简单映射,避免复杂变形。这一特性标志着CDS从单纯消费模型向双向数据交互能力的演进,为ABAP开发提供了新的数据边界设计思路。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1313

实战化C++后台开发工具链:从编码到部署的全流程效率提升

在C++后台开发领域,一套高效的工具链是保障工程质量和开发效率的核心基础设施。其核心原理在于通过自动化、标准化的工具集合,覆盖从编码、构建、调试到测试、部署的完整开发生命周期。从技术价值看,它不仅能显著减少环境配置和低级错误排查的时间消耗,更能通过静态分析、性能剖析等手段,在早期发现潜在缺陷与性能瓶颈,提升代码的健壮性与可维护性。在应用场景上,无论是处理高并发网络服务,还是构建复杂的业务逻辑系统,一套集成了现代CMake、智能编辑器、GDB/Valgrind调试分析工具以及CI/CD流水线的工具链,都能帮助

weixin_33691817的博客 330

天猫精灵设备拆机及系统分析

我手头的这个天猫精灵型号为TG_X1,黑色天猫精灵AI助手,拆解没有多少难度,但需要注意连接线的问题。具体的拆机过程可以参见:http://www.geekifix.com/wp/archives/415.html注意一点就是,天猫精灵的主板的位置,位于音频按键的底部,也就是顶端,而拆解是从电源的位置从低到顶进行拆机;拆机后,看到主控板的信息如下:主控型号:联发科 MT8516MT8516支持四核...

LeeKwen的专栏 3万+

从C代码到硬件执行:实践《深入理解计算机系统》的底层编程指南

在计算机科学中,理解程序从高级语言到硬件执行的完整链路是提升开发者核心能力的关键。这一过程涉及编译原理、内存管理、操作系统和计算机组成等多个基础概念的协同工作。通过掌握编译系统的预处理、编译、汇编和链接四个阶段,开发者可以深入理解代码如何转化为机器指令。异质的数据结构(如结构体和联合体)在内存中的布局与对齐,直接影响程序性能和数据访问效率,是连接高级语言抽象与底层硬件内存模型的重要桥梁。理解虚拟内存、进程地址空间以及堆栈管理,能够帮助开发者编写更高效、更安全的代码,并有效调试段错误、内存泄漏等底层问题。本文

weixin_30565199的博客 362

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Helgrind:螺纹错误检测器

目录 7.1。概观7.2。检测到的错误:POSIX pthread API的滥用7.3。检测到的错误:锁定排序不一致7.4。检测到的错误:数据竞赛 7.4.1。简单数据竞赛7.4.2。Helgrind的种族检测算法7.4.3。解释竞赛错误讯息 7.5。有效使用Helgrind的提示和提示7.6。Helgrind命令行选项7.7。Helgrind监视器命令7.8。Helgrind客户端请求7.

swartz_lubel的专栏 903

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

valgrind内存泄露和线程竞态检测(1)

分享一份NDK基础开发资料分享内容包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

2301_78416573的博客 1195

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

上一篇: Valgrind 并发调试 :DRD 实战—死锁与锁序一网打尽
下一篇: 嵌入式工程师必修课:打通 AI 与嵌入式的核心逻辑与实践
嵌入式孙老师
博客等级 码龄7年 5687粉丝 902原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值