volatile关键字

本文深入探讨了C/C++中volatile关键字的作用与用法,通过对比分析优化前后代码的区别,揭示了volatile在多线程环境中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

volatile关键字

原文地址:白衣人

volatile是c/c++中一个鲜为人知的关键字,该关键字告诉编译器不要持有变量的临时拷贝,它可以适用于基础类型
如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者
类的所有成员都会被视为volatile.

使用volatile并不会否定对CRITICAL_SECTION,Mutex,Event等同步对象的需要
例如:
int i;
i = i + 3;
无论如何,总是会有一小段时间,i会被放在一个寄存器中,因为算术运算只能在寄存器中进行。一般来说,volatitle
关键字适用于行与行之间,而不是放在行内。

我们先来实现一个简单的函数,来观察一下由编译器产生出来的汇编代码中的不足之处,并观察volatile关键字如何修正
这个不足之处。在这个函数体内存在一个busy loop(所谓busy loop也叫做busy waits,是一种高度浪费CPU时间的循环方法)

void getKey(char* pch)
{
 while (*pch == 0)
  ;
}

当你在VC开发环境中将最优化选项都关闭之后,编译这个程序,将获得以下结果(汇编代码)
;       while (*pch == 0)
$L27
 ; Load the address stored in pch
 mov eax, DWORD PTR _pch$[ebp]
 ; Load the character into the EAX register
 movsx eax, BYTE PTR [eax]
 ; Compare the value to zero
 test eax, eax
 ; If not zero, exit loop
 jne $L28
 ;
 jmp $L27
$L28
;}

这段没有优化的代码不断的载入适当的地址,载入地址中的内容,测试结果。效率相当的低,但是结果非常准确

现在我们再来看看将编译器的所有最优化选项开关都打开以后,重新编译程序,生成的汇编代码,和上面的代码
比较一下有什么不同
;{
 ; Load the address stored in pch
 mov eax, DWORD PTR _pch$[esp-4]
 ; Load the character into the AL register
 movsx al, BYTE PTR [eax]
; while (*pch == 0)
 ; Compare the value in the AL register to zero
 test al, al
 ; If still zero, try again
 je SHORT $L84
 ;
;}

从代码的长度就可以看出来,比没有优化的情况要短的多。需要注意的是编译器把MOV指令放到了循环之外。这在
单线程中是一个非常好的优化,但是,在多线程应用程序中,如果另一个线程改变了变量的值,则循环永远不会
结束。被测试的值永远被放在寄存器中,所以该段代码在多线程的情况下,存在一个巨大的BUG。解决方法是重新
写一次getKey函数,并把参数pch声明为volatile,代码如下:

void getKey(volatile char* pch)
{
 while (*pch == 0)
  ;
}

这次的修改对于非最优化的版本没有任何影响,下面请看最优化后的结果:

;{
 ; Load the address stored in pch
 mov eax, DWORD PTR _pch$[esp-4]
;       while (*pch == 0)
$L84:
 ; Directly compare the value to zero
 cmp BYTE PTR [eax], 0
 ; If still zero, try again
 je SHORT $L84
 ;
;}

这次的修改结果比较完美,地址不会改变,所以地址声明被移动到循环之外。地址内容是volatile,所以每次循环
之中它不断的被重新检查。

把一个const volatile变量作为参数传递给函数是合法的。如此的声明意味着函数不能改变变量的值,但是变量的
值却可以被另一个线程在任何时间改变掉。

另:

如果在一个多线程程序中想在两个函数中共享一个局部变量,需把变量声明为volatile类型

例如:

void f1(void * x)
{    
while(*(int *)x==0);
    cout<<"helo";
    _endthread();
}
void f2(void *i)
{
for(int j=0;j<111;j++);
    (*((int *)i))++;
    _endthread();
}
int main()
{
    int i=0;
    _beginthread((void(*)(void *))f1,0,&i);    
    _beginthread((void(*)(void *))f2,0,&i);
while(getch()!='q');
return 0;
}

上例中,函数f1()中的循环控制条件永远为真,字符串"helo"得不到打印。

如果把f1()的参数类型改为volatitle void *x,则打印命令被实现。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值