volatile关键字

【C/C++实战篇】volatile的作用是什么? volatile的存储类型、特征修饰、数据类型?volatile三种应用场合 volatile修饰符用于防止编译器优化变量,确保每次访问都从内存读取最新值,适用于中断、多线程和硬件寄存器等易变场景。C语言变量定义包含存储类型(auto/static/register/extern)、特征修饰(如volatile)、数据类型和变量名。volatile可用于中断服务程序、多线程共享标志和硬件寄存器,避免因优化导致错误。但过度使用可能增加代码体积和降低性能。此外,volatile可与const或extern叠加使用,如只读状态寄存器或外部共享变量。合理使用volatile能保证程序 阅读详情
转载
讲讲volatile的作用
5
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
    1). 并行设备的硬件寄存器(如:状态寄存器)
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    3). 多线程应用中被几个任务共享的变量
    回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
    假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
    1). 一个参数既可以是const还可以是volatile吗?解释为什么。
    2). 一个指针可以是volatile 吗?解释为什么。
    3). 下面的函数有什么错误:
         int square(volatile int *ptr)
         {
              return *ptr * *ptr;
         }
    下面是答案:
    1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
    2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
    3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
    int square(volatile int *ptr) 
    {
         int a,b;
         a = *ptr;
         b = *ptr;
         return a * b;
     }
    由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
     long square(volatile int *ptr) 
     {
            int a;
            a = *ptr;
            return a * a;
     }
讲讲我的理解: (欢迎打板子...~~!)

关键在于两个地方:     
 
1. 编译器的优化  (请高手帮我看看下面的理解)

在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;

当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致

当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致

当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致 


举一个不太准确的例子: 

发薪资时,会计每次都把员工叫来登记他们的银行卡号;一次会计为了省事,没有即时登记,用了以前登记的银行卡号;刚好一个员工的银行卡丢了,已挂失该银行卡号;从而造成该员工领不到工资 

员工 -- 原始变量地址 
银行卡号 -- 原始变量在寄存器的备份 


2. 在什么情况下会出现(如1楼所说)

    1). 并行设备的硬件寄存器(如:状态寄存器) 
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 
    3). 多线程应用中被几个任务共享的变量 
    

补充: volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人; 

“易变”是因为外在因素引起的,象多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;

而用volatile定义之后,其实这个变量就不会因外因而变化了,可以放心使用了; 大家看看前面那种解释(易变的)是不是在误导人


------------简明示例如下:------------------

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
使用该关键字的例子如下:
int volatile nVint;
>>>>当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
例如:
volatile int i=10;
int a = i;
...
//其他代码,并未明确告诉编译器,对i进行过操作
int b = i;
>>>>volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
>>>>注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
>>>>首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:
>>
#i nclude <stdio.h>
void main()
{
int i=10;
int a = i;
printf("i= %d",a);
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d",b);
}      
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#i nclude <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf("i= %d",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d",b);
}      
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的作用!

------------------------------------


volatile对应的变量可能在你的程序本身不知道的情况下发生改变
比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
你自己的程序,是无法判定合适这个变量会发生变化
还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,而不会利用cache当中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。

--------------------------------------------------------------------------------

典型的例子
for ( int i=0; i<100000; i++);
这个语句用来测试空循环的速度的
但是编译器肯定要把它优化掉,根本就不执行
如果你写成 
for ( volatile int i=0; i<100000; i++);
它就会执行了

volatile的本意是“易变的” 
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:

static int i=0;

int main(void)
{
...
while (1)
{
if (i) dosomething();
}
}

/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}

程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此
可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被
调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。

一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

2、多任务环境下各任务间共享的标志应该加volatile;

3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实
现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。 
 



By hbtian | 阅读全文(2819) | 回复(2) |反映问题 | 引用通告(0) | 编辑
2008-5-26 18:38:00
Re:讲讲volatile的作用
访客1iy3CO(游客) 优化后DWORD PTR [EBP-4]并非i的地址,程序中已经没有任何局部变量了,可能导致程序崩溃。
另外,在网页中藏毒,不厚道。
By 访客1iy3CO(游客) | 个人主页 | 引用 | 返回 | 删除 | 回复
2008-5-26 18:34:00
Re:讲讲volatile的作用
访客1iy3CO(游客) ::00401000:: 55 PUSH EBP /:BYCALL CallBy:004012C2,
::00401001:: 8BEC MOV EBP,ESP
::00401003:: 6A 0A PUSH A
::00401005:: 68 30704000 PUSH 407030 /->: i= %d/x0A
::0040100A:: E8 D3010000 CALL 004011E2 /:JMPDOWN
::0040100F:: C745 FC 20000000 MOV DWORD PTR [EBP-4],20
::00401016:: 6A 0A PUSH A
::00401018:: 68 30704000 PUSH 407030 /->: i= %d/x0A
::0040101D:: E8 C0010000 CALL 004011E2 /:JMPDOWN
::00401022:: A1 3C704000 MOV EAX,[40703C]
::00401027:: 83C4 10 ADD ESP,10
::0040102A:: 48 DEC EAX
::0040102B:: A3 3C704000 MOV [40703C],EAX
::00401030:: 78 08 JS SHORT 0040103A /:JMPDOWN
::00401032:: FF05 38704000 INC DWORD PTR [407038]
::00401038:: 5D POP EBP
::00401039:: C3 RETN
::0040103A:: 68 38704000 PUSH 407038 /:BYJMP JmpBy:00401030, /->: 濦
::0040103F:: E8 0C000000 CALL 00401050 /:JMPDOWN
::00401044:: 83C4 04 ADD ESP,4
::00401047:: 5D POP EBP
::00401048:: C3 RETN
volatile 有什么作用? 但在多线程环境下,这种优化可能会导致问题,因为一个线程修改了变量的值,但其他线程可能不会立即看到这个变化。的主要作用是告诉编译器和操作系统,该变量可能会被多个线程同时访问,因此不应该对这个变量进行优化,以确保线程之间能够正确地读取和写入这个变量的值。的主要作用是确保多线程环境下对变量的可见性,防止编译器进行优化,但它不提供原子性。变量的值,那么其他线程在读取这个变量的值时会看到最新的修改,而不会使用缓存或旧值。可以确保一个线程对变量的修改对其他线程可见。可以保证可见性,但它并不保证对变量的操作是原子的。 阅读详情

相关推荐

Linux C中多线程与volatile变量

主要介绍了Linux C中多线程与volatile变量的相关资料,需要的朋友可以参考下

讲讲volatile的作用

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:     1). 并行设备的硬件寄存器(如:状态寄存器)     2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic var

少帅的天空 1404

【AGC】【serverless】【云函数】云函数常见问题

1、云函数添加了AUTH触发器后,在用户执行注册、销户、登录、登出动作时,云函数的event对象会自动触发返回相应信息。比如我在“层”的压缩包里放了config/config.txt,那么我在云函数里怎么调用这个文件?层的目录是:/dcache/layer,函数的目录是/dcache/layer/func。在云函数里往云数据库里插入数据返回的是180000,这是正常吗?已经按照文档在后台对应的云函数里配置了,所需的AUTH触发器。层的用法和放在函数里的用法是相同的。云函数里的层如何调用?

华为开发者联盟 839

volatile的作用总结与理解

文章目录1. 前言2. `volatile`的作用:2.1 使用`volatile`来解决多核`CPU`高速缓存导致的变量不同步问题;2.2 `volatile` 还可以解决指令重排序问题2.3 `volatile`不能保证操作的原子性 1. 前言 volatile这个关键字比较重要,尤其是在看多线程的时候,会经常出现。那么就来看下这个关键字到底有什么用。 volatile这个关键字的引入是为了线程安全,但是volatile不保证线程安全。 在多处理器的系统中(或者单处理器多核的系统),每个处理器(每个核

梦否 6454

详解Σ-Δ型ADC拓扑结构的基本原理

现在,总环路滤波器的H(f)具有更大的滚降,Mod 2型的噪声传递函数具有40 dB/十倍频程的上升速率。同样,当正弦波输入为负满量程时,+1和–1之间的转换会减少,输出以–1为主。噪声传递函数是一个高通滤波器函数,提供噪声整形,在DC附近的较低频率,对量化噪声有很强的抑制。H(f)是环路滤波器的函数,定义噪声和信号的传递函数。Σ-Δ型ADC的噪底由热噪声决定,而不受量化噪声的限制。量化噪声为量化器的输入与输出之差。图5. Mod 1 Σ-Δ环路的线性模型(b),包括方程、滤波器、信号和噪声传递函数图。

qq_43416206的博客 1810

ObjectMapper解析JSON数据

的时候捕获的数据,往往需要结合文档所定义的类进行转换,也就是。库中的一个关键类,它的作用是将。对象之间进行相互转换。

weixin_57128596的博客 975

C#关键字volatile

在这个例子中,count字段被声明为volatile。在这里,hasData变量被声明为volatile,确保了生产者线程对hasData的修改能够被消费者线程及时看到,并且消费者线程对hasData的修改也能被生产者线程及时看到,从而保证了生产者 - 消费者模式的正确运行。例如,有两个线程,一个线程(线程 A)在不断地更新一个共享变量的值,另一个线程(线程 B)在读取这个共享变量的值。因为它要求对变量的每次访问都直接从主内存中进行,而不是从线程的工作内存(缓存)中进行,这会增加内存访问的开销。

2401_83367969的博客 2148

Spring AI + RAG 构建电商智能客服:从 PDF 文档解析到精准问答的全链路实战

核心框架:向量数据库:- PostgreSQL + pgvector (生产推荐)- Redis Stack (轻量级方案)- ChromaDB (快速原型)文档解析:- Apache PDFBox: PDF 文本提取- Spring AI Document Readers: 结构化解析大模型:- 阿里云百炼 (DashScope)- 本地部署:Qwen/ChatGLM/*** 电商领域实体*/@Data@Builder@ToString/*** 商品类型。

一起修行,不孤单。这里有编程语言、开发工具、学习方法论和踩坑笔记。用简单的话说复杂的事,陪你从小白到进阶。周更干货,欢迎一起成长! 381

volatile关键字详解

缓存一致性协议: 每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。在多线程环境下,CPU轮流切换线程执行,有可能某个线程修改了数据,准备回主内存,此时CPU切换到另一个线程修改了数据,并且写回到了主内存,此时就导致数据的不准确。的作用就是,当某个线程工作内存中的值与主内存中的值,如果不相同就会一直。

努力分享一些干货 1837

SystemVerilog从入门到实战

SystemVerilog复合数据类型概述 SystemVerilog增强了Verilog的数据建模能力,提供多种复合数据类型: 数组类型:包括定宽数组(固定大小)、动态数组(运行时分配)和多维数组,支持高效索引访问。 队列:结合数组和链表特性,支持两端快速插入/删除操作,提供push/pop等方法。 关联数组:键值对存储结构,支持稀疏数据存储和快速查找,提供exists/delete等方法。 结构体:组合不同类型数据成员,支持打包(packed)形式以优化存储空间。 枚举类型:为数值赋予语义名称,支持状态

2301_79143213的博客 1225

MicroCap声学仿真介绍(二)-创建声学仿真元件

声学库里包含孔、管、障板辐射器等器件,我们只需要按照仿真的需求,将声学库里的器件排列好,输入参数,用线连接起来即可。这里我们用D指代孔的直径,L指代孔的长度,N代表孔的数量。26.在弹出的界面中做如下设置:需要先在右面的选框里找到对应的项,然后右键Add Part,再之后就如下设置。3.在弹出的界面中,选择“Macro file(.mac)”,并点击右上角的“OK”下面就以声学仿真中常见的“孔”来做示例,跟大家一块演示一下宏程序的新建。10.在上图中“Value”中填入“R”,然后点击左边的“OK”按钮;

山无忧的博客 2198

什么时候使用volatile关键字?

想必大家平时都见过volatile关键字,可是你知道什么时候需要使用volatile关键字吗? 直接看下面代码: int a = 100; while (a == 100) { // code } 这段程序编译时,如果编译器发现程序始终没有企图改变a的值,那它可能就会优化这段代码,变成while(true)的死循环使得程序执行的更快,然而编译器有时候也会做过度优化,它有时候可能没有意识到程序会改变a的值,却做了这种优化导致程序没有产生预期的行为。 这里为了产生预期的行为,需要阻止编译器做这种优化

qq_29426201的博客 2406

AI视频生成工作流开发:从产品拆解到带货视频全流程实现

本文介绍了一套完整的AI视频生成工作流开发方案,该系统能够自动分析产品素材并生成9个标准化分镜,支持白底图智能替换,最终输出专业带货视频。系统基于Coze平台搭建,整合豆包大模型及多模态生成模型,实现从产品分析、脚本生成到视频合成的全流程自动化。关键模块包括产品特征提取、智能分镜分配、视觉内容生成和用户交互调整,支持手动优化提示词和输出结果。该方案采用模块化设计,结合多种AI模型协同工作,为电商视频制作提供高效解决方案。

huanghm88的专栏 384

浅析volatile关键字

volatile 关键字声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)const 可以是 volatile(如只读的状态寄存器)指针可以是 volatile。

螺蛳粉只吃炸蛋的走风的博客 1052

190、LLC谐振变换器的样机调试实战(问题定位)

这篇实战笔记总结了LLC谐振变换器样机调试中的五大典型问题及解决方案:1)空载炸机源于谐振电容纹波电流能力不足;2)轻载啸叫因控制环路不稳定,需调整突发模式阈值;3)MOSFET过热由死区时间不当导致,需精确优化实现ZVS;4)输出纹波大由PCB布局耦合引起,需优化共模抑制;5)启动过冲需延长软启动时间。作者强调调试需结合示波器、热成像仪等工具,理论计算与实测并重,并详细记录每次参数调整。文章指出LLC调试是不断试错的过程,关键是通过现象分析本质原因。

Jack010169的博客 48

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

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

volatile关键字的作用

在我们了解volatile关键字之前,首先看一下代码; public class Mythread implements Runnable{ private boolean flag = false; @Override public void run() { try { //当前线程让出资源 sleep(20...

qq_37031675的博客 5060

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

C++中volatile关键字的作用

对于较官方的解释是:volatile作为指令关键字,确保本条指令不会因编译器的优化而被省略,即系统每次从变量所在内存读取数据而不是从寄存器读取备份。 下面将进行详细说明: 易变性 编译器对volatile修饰的变量,当要读取这个变量时,任何情况下都会从内存中读取,而不会从寄存器缓存中读取(因为每次都从内存中读取体现出变量的“易变”) 对于非volatile变量 对 b = a + 1;这条语句,...

liufangping的博客 4973

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

14. Java中 volatile 关键字

volatile 关键字概念介绍,从整体层面了解 volatile 关键字;volatile 关键字与 synchronized 关键字的区别,这是本节的重点内容之一,了解 volatile 关键字与 synchronized 关键字的区别,才能更好地区分并掌握两钟关键字的使用;volatile 关键字原理介绍,也是本节课程的重点之一;volatile 关键字的使用,是本节课程的核心内容,所有的知识点都是围绕这一目的进行讲解的。概念。

这是一个web全栈开发的博客,取其精华去其糟粕,争取让大家一看就懂,一学就会,一用就成~ 1279

volatile关键字总结

c++的volatile关键字

papaya的博客 3216

对 volatile 关键字的理解

对 volatile 关键字的理解

技术回忆录 1757

volatile关键字的作用、原理

综上,其实volatile保持内存可见性和防止指令重排序的原理,本质上是同一个问题,也都依靠内存屏障得到解决。更多内容请参见JVM相关书籍。volatile关键字的作用、原理。

攻城狮不是猫 1084

volatile 关键字

文章目录1 volatile关键字的两层语义2 volatile不能保证原子性3.volatile能保证有序性吗?4 使用volatile关键字的场景4.1 状态标记量4.2 double check 1 volatile关键字的两层语义 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的 禁止进行指令重排序 先看一段代码,假如线程1先执行,线程2后

ly_jing的博客 275
上一篇: VMware 中linux 与主机 xp文件互访
下一篇: memset /memcpy /strcpy函数的使用与区别
Coding人生
博客等级 码龄20年 37粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值