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
可以保证对特殊地址的稳定访问。
static
1、概述
static
声明的变量在C
语言中有两方面的特征:
1)、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
2)、变量用static
告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。
2、问题:Static
的理解
关于static
变量,请选择下面所有说法正确的内容:
A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
D、静态全局变量过大,可那会导致堆栈溢出。
答案与分析:
对于A,B:根据本篇概述部分的说明b),我们知道,A,B都是正确的。
对于C:根据本篇概述部分的说明a),我们知道,C是正确的(所谓的函数重入问题,下面会详细阐述)。
对于D:静态变量放在程序的全局数据区,而不是在堆栈中分配,所以不可能导致堆栈溢出,D是错误的。
因此,答案是A、B、C
。
3、问题:不可重入函数
曾经设计过如下一个函数,在代码检视的时候被提醒有bug
,因为这个函数是不可重入的,为什么?
unsigned int sum_int( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是static类型的。
for (index = 1; index <= base; index++)
{
sum += index;
}
return sum;
}
答案与分析:
所谓的函数是可重入的(也可以说是可预测的),即:只要输入数据相同就应产生相同的输出。
这个函数之所以是不可预测的,就是因为函数中使用了static
变量,因为static
变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static
变量,这种函数中的static
变量,使用原则是,能不用尽量不用。
将上面的函数修改为可重入的函数很简单,只要将声明sum
变量中的static
关键字去掉,变量sum
即变为一个auto
类型的变量,函数即变为一个可重入的函数。
当然,有些时候,在函数中是必须要使用static
变量的,比如当某函数的返回值为指针类型时,则必须是static
的局部变量的地址作为返回值,若为auto
类型,则返回为错指针。
.net
里为什么尽量避免Volatile
关键字
- 需要应用的场景往往是多线程,多,是指多于2个。应用场景很重要,如果那
volatile
修饰一个用于累加的计数器,那是很糟糕的,它本身不是用来设计做这个。在.net
中应该去找Interlocked
帮忙。volatile
的作用是它可以保证线程之间的可见性(可以理解为1号线程写完了后2号负责读的线程是可见的,所以2号读线程始终可以得到最新的值)和指令的顺序性(专治乱序问题,weak ordering
) volatile
与指令序列有着孽缘,比如说对两个变量的两条赋值语句并不一定是按照这个顺序来执行的。同时,对cpu来讲,CPU
的Lx
缓存在x86/x64
上是保持一致的(x64
兼容x86
,所以脾气跟x86
一样),而在IA64
是每个CPU
独立维护自己的Lx
缓存。volatile
的作用是清掉这个Lx缓存,直接指向内存。然后自己用volatile read
和volatile write
去操作数据。Volatile
并不是想象中的那么高效。很多文章总拿Volatile
和lock
去比,那当然是不具备可比性的。最早我们都讨论singleton
实现的时候使用double-check
机制,利用volatile
,其实不如用MemoryBarrier
合算。MemoryBarrier
就是为惩治乱序问题而诞生的。Monitor、Mutex
啥的都是基于保证Lx
缓存的同步来做到的。
结论:如果要维护支持乱序指令的cpu
上需要保证某个简单操作不会因为乱序问题而遭受影响的话,那使用volatile
。但是这种情景如果还要追求性能的话,别相信别人的话,先去试试MemoryBarrier
。
volatile
并不保证thread-safe
,这个例子可以看到:
MSDN上对于volatile
的说明是:
The volatile modifier is usually used for a field that is accessed by multiple threads without using the lock statement to serialize access
最后,Thread
类有个VolatileRead()
方法的