深入变量的“案发”现场

告别复位:巧用KEIL5实现MCU现场调试,精准捕获程序卡死瞬间 本文详细介绍了如何利用KEIL5实现MCU现场调试,避免复位操作导致的关键数据丢失。通过配置热连接(Hot Attach)技术,开发者可以在程序卡死时精准捕获内存和寄存器状态,有效解决内存泄漏、死锁等疑难问题。文章包含硬件准备、工程配置、调试脚本等实用技巧,帮助提升嵌入式调试效率。 阅读详情
深入变量的“案发”现场
 作者:杨小华
       当《绝对能够测试你的C语言功力的几个问题》第一次出现在优快云首页时,我就进入了张老师Blog。客观上说,出的题目比较基础,但每一题都说出一个所以然来,恐怕不是很简单。过了几天就贴出了《语言测试题的讲解分析》,我怀着好奇的心情进去看了看。发现里面赞扬的也有,诋毁的也有。韩愈《师说》里面讲过:闻道有先后,术业有专攻。张老师自然有他的可取之处,也有不知道的知识点。
       OK,言归正转,现在开始对试题中的第二题进行探讨。题目如下:
int x=35;
char str[10];
strcpy(str,"www.it315.org"/*共13个字母*/);
//问:此时x和strlen(str)的值分别是多少?
       我们先不去探讨答案是多少,但我觉得这题与编译器有关。张老师的答案也不是没有道理,网友秦始皇的回答也有道理。肯定有人开始怀疑了,你到底在说什么?这也对,那也对,究竟什么是对的。好的,我们现在就开始分析。
一、      栈
在具体讲解之前,我们先来明确栈的几个概念:满栈与空栈,升序栈与降序栈。
       满栈是指栈指针指向上次写的最后一个数据单元,而空栈的栈指针指向第一个空闲单元。一个降序栈是在内存中反向增长(就是从应用程序空间结束处开始反向增长),而升序栈在内存中正向增长。
       RISC机器使用传统的满降序栈(FD Full Descending)。如果使用符合X86规定的编译器,它通常把你的栈指针设置在应用程序空间的结束处并接着使用一个满降序栈。用来存放一个函数的局部变量、参数、返回地址和其它临时变量的栈区域称为栈帧(stack frame)。(关于这部分的详细信息请参看我另外一篇文章《通过Linux内核源码看函数调用之前世今生》)。如图1所示:

图 1栈帧布局
二、      目标文件格式
各个系统之间,目标文件格式都不相同。第一个从贝尔实验室诞生的UNIX系统使用的是a.out格式。System V Unix的早期版本使用的是COFF(Common Object File Format 一般目标文件格式)。Windows使用的是COFF的一种变种,叫做PE格式(Portable Executable 可移植可执行)。现代Unix――比如Linux,各种BSD ,以及Sun Solaris――使用的是Unix ELF(Executable and Linkable Format,可执行和可链接格式)。尽管以下的讨论集中在ELF上,但不管是哪种格式,基本的概念都是相似的。
如果变量x和str是局部变量,那么肯定是放在栈中。如果他们两者都是全局变量,那么x放在.data段(.data:存放已初始化的全局变量),str放在.bss段(.bss:存放未初始化的全局变量)。
一个典型的ELF可执行目标文件信息布局如图2所示:
图 2典型的ELF可执行文件格式
       每个程序都有一个运行时存储器映像,如图3所示:
图 3Linux运行时存储器映像
       在Linux系统中,代码段总是从地址0x08048000处开始。数据段在接下来的下一个4KB对齐的地址处。运行时堆在接下来的读写段之后的第一个4KB对齐的地址处,并通过malloc库往上增长。开始于0x40000000处的段是为共享库保留的。用户栈总是从地址0xbfffffff处开始,并向下增长的(向低地址方向增长)。从栈的上部开始于地址0xc0000000处的段是为操作系统驻留存储器的部分的代码和数据保留的。通过这里讲解之后,你应该彻底懂得了满降序栈的含义。
三、      寻址与字节顺序
几乎在所有的机器上,多字节对象都以连续的字节序列存放,对象的地址为所使用的字节序列中最小的地址。比如,一个int型的变量x的地址为0x100,也就是说&x=0x100,那么x的四字节将被存储在内存中的0x100,0x101,0x102和0x103。
某些机器选择在存储器中按照从最低有效位字节到最高有效位字节的顺序存储对象,而
另一些机器则按照从最高有效位字节到最低有效位字节的顺序来存储对象。前者我们称为小尾端(little-endian),比如Intel的机器都采用这种规则,后者称为大尾端(big-endian),如IBM,Motorola等机器。
       假设x类型为int,地址位于0x100处,有一个16进制的值为0x12345678,分别在大尾端和小尾端的存储方式为:
       大尾端:
                                 0x103                  0x102                0x101               0x100
……
78
56
34
12
……
       小尾端:
                                 0x103                  0x102                0x101               0x100
……
12
34
56
78
……
       注意,在字0x12345678中,高位字节的16进制为0x12,而低位字节为0x78。不管是在大尾端机器中,还是小尾端机器中,输出的x的值都为0x12345678。
四、      透过汇编代码看变量存储布局
我们从局部变量和全局变量两个方面来,分别在Windows下的VC++6.0和Linux下的GCC来探讨这个题目。
假设程序如下:
1      #include <stdio.h>
2      #include <string.h>
3     
4      int main()
5      {
6           int x = 35;
7           char str[10];
8           strcpy(str,"www.it315.org"/*共13个字母*/);
9           printf("%d/n",x);
10             return 0;
11      }
       这段程序在VC++6.0中的反汇编代码如下:
1:    #include <stdio.h>
2:    #include <string.h>
3:
4:    int main()
5:    {
00401010   push        ebp
00401011   mov        ebp,esp
00401013   sub         esp,50h
00401016   push        ebx
   栈帧布局    高地址
前一个栈帧
ebp
(x=35)
 ……
……
  (str)
   ……
             低地址
 
 
00401017   push        esi
00401018   push        edi
00401019   lea          edi,[ebp-50h]
0040101C   mov         ecx,14h
00401021   mov         eax,0CCCCCCCCh
00401026   rep stos     dword ptr [edi]
6:        int x = 35;
00401028   mov         dword ptr [ebp-4],23h /*将35压进栈中*/    (1处)
7:        char str[10];
8:        strcpy(str,"www.it315.org"/*共13个字母*/);
0040102F   push        offset string "www.it315.org" (00420020)

00401034   lea          eax,[ebp-10h]    (2处)

00401037   push        eax
00401038   call         strcpy (00401100)
0040103D   add         esp,8
9:        printf("%d/n",x);
00401040   mov        ecx,dword ptr [ebp-4]    (3处)
00401043   push        ecx
00401044   push        offset string "%d/n" (0042001c)
00401049   call         printf (00401080)
0040104E   add         esp,8
10:       return 0;
00401051   xor         eax,eax
11:   }
       从以上代码可以发现,在1处,将x的值35压入ebp-4中,在运行2处之前,已经将字符串的值压入栈中了,然后获取str在栈的地址,即ebp-10h,也就是ebp-16,文中红色箭头所指的对方。装载到eax寄存器中,然后也压入栈中。众所周知,在X86平台上,参数的传递是通过栈帧来实现的,此时调用函数strcpy,将字符串的值拷贝到str的地址处。那么此时如何存放字符串?就是问题的关键所在。大家可能都知道已知str字符串的地址,那么要得到它下一个字符串的值,就是*(str+1),那么答案就出来了。在X86平台上,栈是往下增长的,那么越往高处就是高地址,当进行字符串拷贝时,字符串的地址顺着蓝色的线朝上走。因为该字符串长度为13,所以覆盖了x所在的栈中的值,最后一个字符g也就赋给了x,由于在Intel的机器中,采用的是小尾端存储方式,所以值在栈中的布局如图4所示:

   栈帧布局    高地址
前一个栈帧
ebp
(x)g
 ro.5
31ti
 .www
   ……
             低地址

        图 4字符串在栈中的布局

当运行到3处时,程序将ebp-4处的值,也就是x的值压入栈中,调用printf,所以打印出来的为103(也就是g的值)。
       在Linux环境下,GCC编译器似乎表现的技高一筹,得到的答案是35。下面我们来看反汇编后的代码:
       .file "sttest.c"
       .section   .rodata
       .align 32
.LC0:
       .string     "www.it315.org"
       .string     ""
.LC1:
       .string     "%d/n"
       .text
.globl main
       .type       main, @function
main:
       pushl       %ebp
       movl       %esp, %ebp
       subl     $56, %esp
       andl      $-16, %esp
       movl       $0, %eax
       subl     %eax, %esp
       movl       $35, -12(%ebp)   /*将35放到栈中,即x处*/    (1处)

   栈帧布局    高地址
前一个栈帧
ebp
 ……
 ……
 (x=35)
  ……
   ……
   ……
……
……
……
(str)
……
             低地址
 
 

       movl       $.LC0, 4(%esp)

       leal      -40(%ebp), %eax      (2处)

       movl       %eax, (%esp)

       call     strcpy
       movl       -12 (%ebp), %eax    (3处)
       movl       %eax, 4(%esp)
       movl       $.LC1, (%esp)
       call     printf
       movl       $0, %eax
       leave
       ret
       .size main, .-main
       .section   .note.GNU-stack,"",@progbits
       .ident      "GCC: (GNU) 3.3.5 (Debian 1:3.3.5-13)"
       我们从2处可以发现,str的位置在ebp-40处,取得str在栈中的地址,然后放到堆栈指针处,调用strcpy,此时我们不难发现x的地址和str的地址相差40-12=28,远远大于字符串的长度,所以根本不可能覆盖x的值。如果你将字符串的长度改为29个字符,那么就将会覆盖x的值。
       下面,我们来讨论x和str为全局变量的情况,也就是将6,7两行代码提到第3行处。
       在第二节中,我们讨论过x放在.data段,str放在.bss段,从图三中可以观察出,读写段(.data,.bss)位于低地址处。对于ELF文件,一般会规定代码段的总长度大小,低地址处是.data段,因为.data是已经固定了的,而.bss段是在运行时才会赋值,所以代码段剩下的空间都是.bss的大小,注意此时.bss段的地址大于.data段的地址,所以为.bss中的变量赋值时,根本不可能覆盖.data段的值。把图2倒过来看,大家就会明白了。所以,如果x和str是全局变量,str的值永远不可能覆盖x的值。
五、      总结
其实每一道题目后面都隐藏着很多知识,我们不能只看表面,大概差不多就行了。只要我们深究下去,可以获得比题目本身更多的知识点。
六、      附录
在Windows中,程序运行时存储器映像的资料比较少,我到目前只在《编程卓越之道:深入理解计算机》一书中提及过。所以凭我的印象画下了该图:
图 5程序在Windows中运行时的存储器映像
 
告别复位:巧用KEIL5实现MCU现场调试,精准定位程序卡死点 本文详细介绍了如何利用KEIL5实现MCU现场调试,避免传统复位调试破坏程序卡死现场。通过禁用自动复位功能、配置Loader.ini文件以及智能设置断点等技巧,开发者可以精准定位程序卡死点,提高调试效率。文章还提供了常见问题排查指南和进阶调试技巧,帮助开发者应对复杂调试场景。 阅读详情

相关推荐

传智播客张孝祥老师的经典

传至博客张孝祥老师的经典,适合有基础的初学者学习,高清版本

沉重哀悼张孝祥老师

听到您辞世的信息,身感痛心。从大一接触您制作的Java,C语言系列教程,开始了解您。现如您的离开,是IT精英界的一大损失,您“为千万人少走弯路而著书、为中华软件之崛起而讲课”的理念,深深打动每一次渴求在IT里面探索的学子心。 也是看了您的著作《Visual C++基础与案例开发详解》,才开始在C++上取得巨大的突破与进展。感谢您的指点,2010年能够著书《Visual C++2010开发权威...

tubaluer 258

记一次springboot集成海康威视SDK过程

一开始没有接触过物联网这块,走了很多弯路,后面还是自己一点点摸索出来的。有个项目,需要对海康的门禁设备进行集成,后台统一管理设备,进行设备人脸数据的下发、删除等操作。3.我还接入了门禁设备的事件监听,门禁所有的事件都会上报到后台服务,这块代码有需要的可以找我。在linux系统中,sdk我使用的是绝对路径,把Linux版本的文件放到服务时,Acs文件夹下的 各个Manage都有现成的代码,可以根据自己的需要调用即可。路径也不要有中文,切记!2.sdk的存放路径不要有中文,不然会出现很多奇怪的问题。

huang_xiaoen的博客 2350

绝对能够测试你的C语言功力的几个问题!

绝对能够测试你的C语言功力的几个问题!另外,在实际的VC++教学中,发现很少有真正精通了C语言编程的学员,一般都有或多 或少概念不是完全清楚的问题,特别是一些需要丰富的实战经验才能体会和明白的问题,如字符串,指针,类型转换,定义指向函数的指针类型,这也是导致学习 VC++困难的一个原因。下面有几个简单测试将能发现你对C语言的掌握情况。int x=35;char str[10];//

网络空间安全与企业信息化 2166

使用ONNX Runtime在Java Web应用中部署深度学习模型

当应用场景需要集成深度学习模型进行推理时,直接在Java Web应用中集成深度学习框架可能会面临性能、兼容性等问题。为了将深度学习模型无缝集成到Java Web应用中,是一项广受认可且实用的方法。本篇文档将介绍如何将模型转换为ONNX格式,并通过ONNX Runtime Server进行部署,并通过Java Web应用调用以进行回归或预测任务。本方案的主要目标是实现以下功能:将PyTorch模型转换为ONNX格式。部署ONNX Runtime Server,加载ONNX模型并提供推理服务。

shysea2019的博客 8859

霍尔电流传感器在新能源汽车中的应用综述

本文旨在全面剖析霍尔电流传感器在新能源汽车领域的应用现状、技术挑战及发展趋势。通过系统梳理其在电池管理系统、电机控制、车载充电等核心系统的应用场景,深入分析面临的技术瓶颈与创新解决方案,并基于最新技术动向展望未来发展方向,为相关领域的技术创新提供参考依据。

m0_46819860的博客 2260

蓝桥杯2018年省赛——c组纯C编写答案(部分)

标题:哪天返回 小明被不明势力劫持。后莫名其妙被扔到x星站再无问津。小明得知每天都有飞船飞往地球,但需要108元的船票,而他却身无分文。 他决定在x星战打工。好心的老板答应包食宿,第1天给他1元钱。 并且,以后的每一天都比前一天多2元钱,直到他有足够的钱买票。 请计算一下,小明在第几天就能凑够108元,返回地球。 #include<stdio.h> int main() { int c=2,day=1,i=1; while(i<=108) { i+=c; c+=2;

文情 1014

信捷 XDH Ethercat 正负极限配置详解与实战技巧

本文详细解析了信捷XDH系列PLC通过EtherCAT总线配置伺服电机正负硬极限的完整流程与实战技巧。内容涵盖硬件开关选型安装、EtherCAT IO信号映射、软件参数设置(包括关键的极性配置)、调试验证方法,以及硬极限与软极限的协同策略。重点阐述了如何避免常见配置错误,确保运动控制系统的最后一道物理安全防线可靠有效,为工程师提供从理论到实践的全面指导。

wdx012345的博客 601

2018年省赛第九届蓝桥杯真题Java(C组)

第一题:哪天返回    小明被不明势力劫持。后被扔到x星站再无问津。小明得知每天都有飞船飞往地球,但需要108元的船票,而他却身无分文。他决定在x星战打工。好心的老板答应包食宿,第1天给他1元钱。并且,以后的每一天都比前一天多2元钱,直到他有足够的钱买票。请计算一下,小明在第几天就能凑够108元,返回地球。要求提交的是一个整数,表示第几天。请不要提交任何多余的内容。第二题:猴子分香蕉    5只猴...

qq_15720169的博客 1万+

EETI_eGTouch驱动移植及校准

一.环境 芯片:NXP IMX6Q 内核版本:4.1.15 uname:Linux myimx6ek314 4.1.15-myimx6-g5d588fa6-dirty #4 SMP PREEMPT Tue Nov 17 17:26:34 CST 2020 armv7l armv7l armv7l GNU/Linux 二.官方《EETI_eGTouch_Linux_Programming_Guide_v2.5l.pdf》文档勘误 HID_MULTITOUCH =Y or N(官方文档设置为M): 正确设置H

Garden的博客 4335

macOS 12.7.6部署Ollama+Dify避坑指南

本文介绍了在macOS 12.7.6系统下部署Ollama和Dify的详细步骤及避坑指南。主要内容包括:1)关闭系统完整性保护(SIP)以允许修改系统目录;2)安装特定版本的Docker(2.1.0.5)和docker-compose(v2.38.2)以避免兼容性问题;3)配置Ollama服务并下载nomic-embed-text和deepseek-r1:7b模型;4)针对macOS调整Dify的docker配置文件,包括延长超时时间、修改存储路径、增加资源限制等关键设置。文中提供了完整的命令操作步骤和配置

Hi20240217的博客 3106

QTP场景恢复

&lt;wbr&gt;&lt;/wbr&gt; 在调试QTP脚本过程中发现,无论是Qtp自带的运行报告还是TD提供的运行报告,都无法为错误定位提供很确切、直观的参考。比如,有时错误发生在点击“订单填写”的步骤,而运行报告给出的结果是无法找到点击“订单填写”步骤之后的订单填写界面对象的缺失。因此为了更好地保留“现场”,我们在场景恢复关闭进程函数之前,先调用桌面截图函数保留“案发现场”。de...

系统运维 116

LLCC68芯片系统架构与LoRa驱动开发核心解析

LoRa射频收发器是低功耗广域物联网(LPWAN)的关键组件,其本质是集成了调制解调器、协议引擎、数据缓冲区与电源管理的专用SoC。理解其工作模式状态机(Sleep/Standby/TX/RX)与SPI主机接口时序规范,是实现稳定通信的基础。LLCC68通过寄存器映射与命令集双接口抽象硬件控制,支持LoRa扩频调制与FSK兼容模式,具备高链路预算(157 dB)和DC-DC/LDO动态电源切换能力。典型应用场景包括远距离传感器节点、智能表计与工业无线监测系统。本文深入剖析LLCC68的信号链路径、中断机制与

weixin_29859471的博客 284

第三章:内存泄漏的常见“案发现场”

内存泄漏

Xin_ye10086的博客 160

EMC辐射整改常用的4种整改方法

例如,产品设计研发工程师们根据需求,设计出效果良好的滤波电路,置入产品I/O(输入/输出)接口的前级,可使因传导而进入系统的干扰噪声消除在电路系统的入口处;通过选择元器件和合理安排的电路系统,使干扰的影响减少。有些频率点是通过电路板上走线分布参数所决定的,通过前述方法不大有用,此类整改通过在走线中增加小的电感、电容、磁珠来改变电路参数结构,使其移到限值要求较高的频率点上。电线电缆的分类整理在电子设备中,线间耦合是一种重要的途径,也是造成干扰的重要原因,因为频率的因素,可大体分为高频耦合与低频耦合。

学海无涯的专栏 3124

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

凶案现场

题解 六 哎!今天本来不打算再写题解的,本来准备复习数学去了,但是今天C++老师突然又说了我们要赶快完成一个任务,让我们赶快完成,所以我今晚又不能复习数学了。然后我就在完成老师的任务,啊!那个任务好烦啊,是学习通上面的章节测验,一开始以为不需要做的,但是老师到了期末又通知我们赶紧做,然后我吃了晚饭就开始干了,干了两个小时,心好累,倒不是说做不动,而是那个系统真不好,答案一点都不人性化,比如有一个题目:“如何判断y是一个奇数”,我回答的是:“y%2==1”,然后显示:“答案错误”,答案居然是:”y%2!=0"

weixin_52985599的博客 292

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

当Linux程序“跑路”:利用Backtrace快速精准定位“案发现场”

回顾整个利用 backtrace 定位分析 Linux 程序异常退出的过程,它就像是一场有条不紊的解谜之旅。当程序毫无征兆地异常退出时,我们首先要明确这可能是由多种原因导致的,如内存溢出、空指针引用、文件读写错误、系统资源不足、代码逻辑错误或系统异常等。这就需要我们像侦探一样,不放过任何一个可能的线索。接着,我们要借助 backtrace 这个强大的工具。

深度Linux 1931

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

VC++使用pdb和dump恢复“案发现场”

目录   pdb文件 PDB文件简介  EXE、DLL等与pdb文件的匹配 编译器产生符号的过程 Release程序生成pdb文件  dump文件 使用背景介绍 dump文件的生成 调试dump文件 VS调试  本地dump调试 无源代码dump调试 WinDbg调试 pdb文件 PDB文件简介 pdb符号文件是连接二进制指令和源代码之间的纽带,没有符号你所面对只...

学习使我快乐 6344

告别瞎猜!用WinDbg和.pdb符号文件深挖C++程序崩溃的“案发现场”

本文深入探讨了如何使用WinDbg和PDB符号文件分析C++程序崩溃的.dmp文件,提供从基础配置到高级调试技巧的全面指南。通过内存状态检查、多线程竞争分析和符号文件深度应用,帮助开发者精准定位崩溃根源,提升调试效率。

weixin_30856725的博客 396

当Linux程序“跑路”:利用Backtrace快速精准定位“案发现场”(转)

backtrace,中文可译为 “回溯”,是一个用于生成函数调用栈的工具,在程序崩溃或者出现异常时,可以通过 backtrace 来获取函数调用栈信息。这些信息就像是程序运行的 “脚印”,记录了程序执行的路径,能帮助我们了解到程序的执行流程,定位问题发生的位置。在程序运行过程中,函数之间会相互调用,形成一个调用链。当程序出现异常退出时,我们往往不知道问题出在哪里。

oushaojun2的专栏 1363

Node 案发现场揭秘 —— 未定义 “window” 对象引发的 SSR 内存泄露

大厂技术高级前端Node进阶点击上方程序员成长指北,关注公众号回复1,加入高级Node交流群泄露现象在生产中遇到一枚全新的内存泄露场景,因为涉及到相对偏少的 react ssr,...

程序员成长指北 1368

案发现场:被注入的软件及 ORA-600 16703 灾难的恢复

最近帮助一个客户恢复数据库,遇到了如下这个问题。让我们再一次惊醒于数据安全,如果不做好防范,问题总是会来得猝不及防。 客户在尝试启动数据库时,是这样一个 ORA-600 错误映入眼帘,反复重试无法消除问题,历史备份,同样存在问题,客户毫无防范的,陷入一场数据库灾难: SQL*Plus: Release 11.2.0.4.0 Production on Fri Jul 20 2...

木苒的博客 1363

误执行了 rm -fr /* 之后,除了跑路还能怎么办?!

公众号关注“GitHubDaily”设为 “星标”,每天带你学编程!前言临近假期,小林开始飘了。写个简单的 Bash 脚本都不上心了,写完连检查都不检查,直接拖到到实体服务器跑。结果一...

GitHubDaily 957

为甚serve 修改dev不能跑_我删库了...要跑陆么?

最近怎么了,写个简单的 Bash 脚本都不上心了,写完连检查都不检查,直接拖到到实体服务器跑。结果一跑起来,发生不对劲,怎么一个简单脚本跑了 10 秒还没结束,于是立马直接ctrl + c 一顿操作停掉了运行中脚本。接着,习惯性的输入了 ls,结果 what?找不到 ls 命令?瞬间背后一凉,慌慌张张打开了脚本。发现问题了,小林我写了个巨蠢的 Bug,间接执行了 rm -fr /* ,这不意味着我...

weixin_39715538的博客 89
上一篇: 世上最经典的25句话
下一篇: CPU亲合力(CPU Affinity)
normalnotebook
博客等级 码龄24年 647粉丝 38原创
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值